MySQL: Update SET and WHERE both determined by one subselect - mysql

I don't have any idea how to do this..
I have a table like this:
account_categories
--------------------
id | description
--------------------
34 | Home Services
35 | Home Services
36 | Home Services
39 | Home Design
40 | Home Design
I have another table (accounts) that references account_categories.id and it uses all of the above values. :/
I want to flatten account_categories, so I need to pick one duplicate from account_categories and update accounts so that all duplicates use the one selected value.
For instance, I need to turn this:
accounts
---------------------
id | accountCategory
---------------------
1 | 34
2 | 35
3 | 36
4 | 39
5 | 40
Into this:
accounts
---------------------
id | accountCategory
---------------------
1 | 34
2 | 34
3 | 34
4 | 39
5 | 39
I can select an id and distinct description from account categories like this:
SELECT DISTINCT (description), id
FROM crmalpha.account_categories
GROUP BY description
But I guess that the next step is to do something like this:
for ( row in ( SELECT DISTINCT (description), id FROM crmalpha.account_categories GROUP BY description ) ) {
UPDATE crmalpha.accounts SET accountCategory = $row['id'] WHERE accountCategory IN ( SELECT id FROM crmalpha.account_categories WHERE description = $row['description] )
}
Forgive the for loop and php variable pseudo code, I'm just trying to think through it logically. I have no idea how to accomplish this in pure SQL.
Any ideas?
PS., Afterwards, I will go through and delete from account_categories every row where the ID is not used in the accounts table.

This worked when I tried it against the test data you posted above. That said, when doing any mass cleanup like this I'd recommend making a copy of the table first. Also check results after issuing the UPDATE and before issuing a COMMIT.
Here's the query:
UPDATE Accounts acct
INNER JOIN Account_Categories cat ON acct.AccountCategory = cat.id
INNER JOIN (
SELECT MIN(id) AS NewID, Description
FROM Account_Categories
GROUP BY Description) NewCat ON cat.Description = NewCat.Description
SET acct.AccountCategory = NewCat.NewID
Some explanation:
The subquery (SELECT MIN(id)...) gets a single ID value (the lowest one) for each description.
The first join (to Account_Categories) associates each account with its category for the sole purpose of having the decription available.
The second join (to the subquery) associates the account's existing description to the table of flattened/de-duped descriptions and their ID.

Related

MYSQL - Select Unique Common Columns between two tables - Most Efficient Query

I have two tables:
db_contacts
Phone | Name | Last_Name
--------------------
111 | Foo | Foo
222 | Bar | Bar
333 | John | Smith
444 | Tomy | Smith
users_contacts
User_ID | Phone
--------------------
1 | 123
1 | 111
2 | 222
2 | 333
3 | 111
3 | 333
4 | 444
Notice from above that:
User with ID 2 is the only one that have the phone number 222
User with ID 4 is the only one that have the phone number 444
I need to obtain these results with a MySQL query.
In other words: How can I select all the users that have a unique phone number in condition that this number exists in the db_contacts.
I need my end result to be something like that:
User_ID | Phone | Name | Last_Name
------------------------------------
2 | 222 | Bar | Bar
4 | 444 | Tomy | Smith
PS: There is no Foreign key between the Phone columns, as a User can have a phone that is not in the db_contacts.
In real life, db_contacts contains about 1 million records and users_contacts about 5 million records.
What I tried and failed and taking a lot of time to execute:
SELECT *
FROM users_contacts
WHERE users_contacts.phone IN (
SELECT users_contacts.phone
FROM `users_contacts`
JOIN db_contacts ON db_contacts.phone = users_contacts.phone
GROUP BY users_contacts.phone
HAVING COUNT(users_contacts.phone) = 1
)
Update:
Thank you for your replies, I have provided my solution that fits my case perfectly.
I think you want:
select uc.*
from user_contacts uc
where not exists (select 1
from user_contacts uc2
where uc2.phone = uc.phone and uc2.user_id <> uc.user_id
);
For performance, you want an index on user_contacts(phone, user_id).
Another method is:
select max(user_id) as user_id, phone
from user_contacts
group by phone
having count(*) = 1;
The not exists version is probably going to be faster.
I would use a simple JOIN with a NOT EXISTS condition. This is usually the most efficient way to check that something has no duplicates ; compared to your solution, this has the advantage of avoiding aggregation.
SELECT uc.User_ID, dc.*
FROM users_contacts uc
INNER JOIN db_contacts dc ON uc.Phone = dc.Phone
WHERE NOT EXISTS (
SELECT 1
FROM users_contacts uc1
WHERE uc1.Phone = dc.Phone AND uc1.User_ID != uc2.User_ID
)
Hint: consider setting the following indexes:
users_contacts(Phone, User_ID)
db_contacts(Phone)
I first would like to thank everyone that posted solutions, they all worked.
But I was a bit crucial on response times, and solutions provided by the fellows took a lot of time to execute, couple of seconds.
In case anyone was having a similar problem, I ended up by creating a new table calling it users_unique_contacts, and created a trigger AFTER INSERT on users_contacts that checks if the newly created contact existed in the users_unique_contacts, if it didn't exist, add it, else remove it as it means the number is not unique anymore.
My Trigger went like this:
BEGIN
IF EXISTS (SELECT 1 = 1 FROM users_unique_contacts WHERE phone = new.phone LIMIT 1) THEN
BEGIN
DELETE FROM users_unique_contacts WHERE phone = new.phone LIMIT 1;
END;
ELSE
BEGIN
INSERT INTO users_unique_contacts (user_id,phone) VALUES (new.user_id, new.phone);
END;
END IF;
END
Now everytime I want the unique numbers of a user, I query the users_unique_contacts and execution time is milliseconds.

update rate for unique productId by each userID

I'm going to implement a method on my own SQL. I have two tables in MySQL. Suppose that each row is updated in the FirstTable and the values of the rate and countView are variable, I'm trying to update them with the same command:
UPDATE FirstTable SET `countView`= `countView`+1,
`rate`=('$MyRate' + (`countView`-1)*`rate`)/`countView`
WHERE `productId`='$productId'
FirstTable:
productId | countView | rate | other column |
------------+-----------+------+-------------------+---
21 | 12 | 4 | anything |
------------+-----------+------+-------------------+---
22 | 18 | 3 | anything |
------------+-----------+------+-------------------+---
But in this way, a user can vote every time he wants to. So I tried to create a table with two columns productId and userID. Like below:
SecondTable:
productId | userID |
------------+---------------|
21 | 100001 |
------------+---------------|
22 | 100002 |
------------+---------------|
21 | 100001 |
------------+---------------|
21 | 100003 |
------------+---------------|
Now, as in the example given in the SecondTable, a user has given to a productId two vote. So I don't want both of these votes to be recorded.
Problems with this method:
The value of the counter is added to each vote.
I can not properly link the SecondTable and FirstTable to manage the update of the FirstTable.
Of course, this question may not be completely new, but I searched a lot to get the right answer. One of the questions from this site came through this method. Using this method, you can manage the update of a table. This method is as follows:
UPDATE `FirstTable` SET `countView`= `countView`+1,
`rate`=('$MyRate' + (`countView`-1)*`rate`)/`countView`
WHERE `productId`='$productId' IN ( SELECT DISTINCT productId, userID
FROM SecondTable)
But the next problem is that even when I use this command, I encounter the following error:
1241 - Operand should contain 1 column(s)
So thank you so much if you can guide me. And I'm sure my question is not duplicate... thank you again.
This fixes your specific syntax problem:
UPDATE FirstTable
SET countView = countView + 1,
rate = ($MyRate + (countView - 1) * rate) / countView
WHERE productId = $productId AND
productId IN (SELECT t2.productId FROM SecondTable t2);
But if two different users vote on the same product, FirstTable will be updated only once. It is unclear if that is intentional behavior or not.
Note that SELECT DISTINCT is not needed in the subquery.
The error is being generated because you can't return 2 fields in an "in" statement. You'll want to use group by:
Try:
IN ( SELECT DISTINCT productId FROM rating group by product, UserID)
Here's documentation to look over for mysql group by if you want: https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

MySQL counting number of max groups

I asked a similar question earlier today, but I've run into another issue that I need assistance with.
I have a logging system that scans a server and catalogs every user that's online at that given moment. Here is how my table looks like:
-----------------
| ab_logs |
-----------------
| id |
| scan_id |
| found_user |
-----------------
id is an autoincrementing primary key. Has no real value other than that.
scan_id is an integer that is incremented after each successful scan of all users. It so I can separate results from different scans.
found_user. Stores which user was found online during the scan.
The above will generate a table that could look like this:
id | scan_id | found_user
----------------------------
1 | 1 | Nick
2 | 2 | Nick
3 | 2 | John
4 | 3 | John
So on the first scan the system found only Nick online. On the 2nd it found both Nick and John. On the 3rd only John was still online.
My problem is that I want to get the total amount of unique users connected to the server at the time of each scan. In other words, I want the aggregate number of users that have connected at each scan. Think counter.
From the example above, the result I want from the sql is:
1
2
2
EDIT:
This is what I have tried so far, but it's wrong:
SELECT COUNT(DISTINCT(found_user)) FROM ab_logs WHERE DATE(timestamp) = CURDATE() GROUP BY scan_id
What I tried returns this:
1
2
1
The code below should give you the results you are looking for
select s.scan_id, count(*) from
(select distinct
t.scan_id
,t1.found_user
from
tblScans t
inner join tblScans t1 on t.scan_id >= t1.scan_id) s
group by
s.scan_id;
Here is sqlFiddle
It assumes the names are unique and includes current and every previous scans in the count
Try with group by clause:
SELECT scan_id, count(*)
FROM mytable
GROUP BY scan_id

export phpList subscribers via sql in mysql database

For some reason, I am unable to export a table of subscribers from my phpList (ver. 3.0.6) admin pages. I've searched on the web, and several others have had this problem but no workarounds have been posted. As a workaround, I would like to query the mySQL database directly to retrieve a similar table of subscribers. But I need help with the SQL command. Note that I don't want to export or backup the mySQL database, I want to query it in the same way that the "export subscribers" button is supposed to do in the phpList admin pages.
In brief, I have two tables to query. The first table, user contains an ID and email for every subscriber. For example:
id | email
1 | e1#gmail.com
2 | e2#gmail.com
The second table, user_attribute contains a userid, attributeid, and value. Note in the example below that userid 1 has values for all three possible attributes, while userid's 2 and 3 are either missing one or more of the three attributeid's, or have blank values for some.
userid | attributeid | value
1 | 1 | 1
1 | 2 | 4
1 | 3 | 6
2 | 1 | 3
2 | 3 |
3 | 1 | 4
I would like to execute a SQL statement that would produce a row of output for each id/email that would look like this (using id 3 as an example):
id | email | attribute1 | attribute2 | attribute3
3 | e3#gmail.com | 4 | "" | "" |
Can someone suggest SQL query language that could accomplish this task?
A related query I would like to run is to find all id/email that do not have a value for attribute3. In the example above, this would be id's 2 and 3. Note that id 3 does not even have a blank value for attributeid3, it is simply missing.
Any help would be appreciated.
John
I know this is a very old post, but I just had to do the same thing. Here's the query I used. Note that you'll need to modify the query based on the custom attributes you have setup. You can see I had name, city and state as shown in the AS clauses below. You'll need to map those to the attribute id. Also, the state has a table of state names that I linked to. I excluded blacklisted (unsubscribed), more than 2 bounces and unconfirmed users.
SELECT
users.email,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=1
) AS name,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=3
) AS city,
(SELECT st.name
FROM `phplist_user_user_attribute` attrs
LEFT JOIN `phplist_listattr_state` st
ON attrs.value = st.id
WHERE
attrs.userid = users.id and
attributeid=4
) AS state
FROM
`phplist_user_user` users
WHERE
users.blacklisted=0 and
users.bouncecount<3 and
users.confirmed=1
;
I hope someone finds this helpful.

MySQL returning arrays from subqueries, and NULL

I have two tables, "records", and "info".
The "records" table looks like:
mysql> SELECT * FROM records WHERE num = '7';
+-----+--------+----+------+-----+-----+------------+-----------+----------+---------------------+
| id | city | st | type | num | val | startdate | status | comments | updated |
+-----+--------+----+------+-----+-----+------------+-----------+----------+---------------------+
| 124 | Encino | CA | AAA | 7 | 1 | 1993-09-01 | allocated | | 2014-02-26 08:16:07 |
+-----+--------+----+------+-----+-----+------------+-----------+----------+---------------------+
and so on. Think of the "num" field in this table as a Company ID.
The "info" table contains information about certain companies, and uses that company id as a unique identifier. Not all companies listed in "records" will be in "info". An example of the "info" table:
mysql> SELECT * FROM info LIMIT 2;
+-----+-------+--------------------------+---------------------+
| org | name | description | updated |
+-----+-------+--------------------------+---------------------+
| 0 | ACME | | 2014-02-19 10:35:39 |
| 1 | AT&T | Some Phone Company, Inc. | 2014-02-18 15:29:50 |
+-----+-------+--------------------------+---------------------+
So "org" here will match "num" in the first table.
I want to be able to run a query that returns, on one line, everything but 'id', 'type' and 'val' from the 1st table, and IF APPLICABLE, the 'name' and 'description' from the 2nd table.
I can achieve what I want using this query:
SELECT city,st,num,startdate,status,comments,updated, \
( SELECT name FROM info WHERE org = '7') AS name, \
( SELECT description FROM info WHERE org = '7') AS description \
FROM records WHERE num = '7'
But I see at least two problems with it:
It seems inefficient to run two subqueries
When there is no record in "info", NULL is printed for the name and
description. I would like to print some string instead.
To address the first problem, I tried to return an array. But when no corresponding record exists in the "info" table, then I get nothing, not even the valid info from the "records" table. Here's my array query:
SELECT city,st,num,startdate,status,comments,updated,asinfo.name AS name,asinfo.description AS description \
FROM records, \
( SELECT name,description FROM info WHERE org = '7') AS asinfo \
WHERE num = '7'
This query works fine if a given company id exists in both tables.
To address the second problem, I tried various incantations of IFNULL and coalesce, to no avail.
I'd appreciate any insight.
Thanks.
Apply LEFT JOIN syntax:
SELECT
r.city,
r.st,
r.num,
r.startdate,
r.status,
r.comments,
r.updated,
IF(d.name IS NULL, 'Default', d.name) AS name,
IF(d.description IS NULL, 'Default', d.description) AS description
FROM
records AS r
LEFT JOIN info AS d ON r.num=d.org
WHERE
r.num='7'
that will work such way: LEFT JOIN looks into first table, and, if there are no corresponding records in second, it applies NULL. So you'll discover that with IF (or IFNULL) and do substitution of default string.
Use a LEFT JOIN to get null values when there's no matching row in the info table.
SELECT city,st,num,startdate,status,comments,updated,
IFNULL(name, 'Default Name') name,
IFNULL(description, 'Default Description') description
FROM records r
LEFT JOIN info i ON r.num = i.org
WHERE r.num = 7
It sounds like a simple LEFT JOIN from record to info will do the trick.
LEFT JOIN rather than JOIN in order to ensure you ALWAYS get all rows from the record table, and then the corresponding data in info table if a xref exists for that ID.
Whether using your sub-queries or using joins, if you always want to see all rows in record table, then you will always get NULLs corresponding to the info table where no xref exists. The only way to avoid that is to run some code that calls everything from record, and then iterates over the results to query info, to conditionally add to the record data.