MySQL select from database where not in - mysql

I need to clean records from one database according to that if they not exists in other.
It's fairly hard to explain it so here is example:
Table Users
-----------
id
username
password
Table Articles
--------------
id
title
created_by
edited_by
created_by and deleted_by contain the user ID.
I have 3-4 tables with almost the same structure like the articles table and I want to delete users from table users who don't have any record in articles-like tables.
I mean users whos ID cannot be found in any of the articles-like tables in the created_by and edited_by table.
How to do that?
I first tried to see if I can select all the data by all the tables according to users, but the server cannot execute the query:
SELECT * FROM `users`
JOIN `articles`
ON `articles`.`created_by` = `users`.`id`
AND `articles`.`edited_by` = `users`.`id`
JOIN `articles_two`
ON `articles_two`.`created_by` = `users`.`id`
AND `articles_two`.`edited_by` = `users`.`id`
JOIN `articles_three`
ON `articles_three`.`created_by` = `users`.`id`
AND `articles_three`.`edited_by` = `users`.`id`
JOIN `articles_four`
ON `articles_four`.`created_by` = `users`.`id`
AND `articles_four`.`edited_by` = `users`.`id`
JOIN `articles_five`
ON `articles_five`.`created_by` = `users`.`id`
AND `articles_five`.`edited_by` = `users`.`id`
JOIN `articles_six`
ON `articles_six`.`created_by` = `users`.`id`
AND `articles_six`.`edited_by` = `users`.`id`;

I think the cleanest way is not in in the select clause:
select *
from users u
where u.id not in (select created_by from articles where created_by is not null) and
u.id not in (select edited_by from articles where edited_by is not null) and
u.id not in (select created_by from articles_two where created_by is not null) and
u.id not in (select edited_by from articles_two where edited_by is not null) and
u.id not in (select created_by from articles_three where created_by is not null) and
u.id not in (select edited_by from articles_three where edited_by is not null) and
u.id not in (select created_by from articles_four where created_by is not null) and
u.id not in (select edited_by from articles_four where edited_by is not null)
Performance should be helped by having indexes on the various created_by and edited_by columns.

This should work. It's not terribly elegant but I think it's easy to follow:
DELETE FROM Users
WHERE ID NOT IN (
SELECT Created_By FROM Articles
UNION SELECT Edited_By FROM Articles
UNION SELECT Created_By FROM Articles_Two
UNION SELECT Edited_By FROM Articles_Two
...
UNION SELECT Created_By FROM Articles_Six
UNION SELECT Edited_By FROM Articles_Six
)
As with any big "cleanup" query, (a) make a copy of the table first and (b) review carefully before typing COMMIT.

In MySQL, left outer join ... where null tends to perform better than in or exists (where there are appropriate indexes), so the following should be worth trying:
SELECT u.* FROM `users` u
LEFT JOIN `articles` ac1 ON ac1.`created_by` = u.`id`
LEFT JOIN `articles_two` ac2 ON ac2.`created_by` = u.`id`
LEFT JOIN `articles_three` ac3 ON ac3.`created_by` = u.`id`
LEFT JOIN `articles_four` ac4 ON ac4.`created_by` = u.`id`
LEFT JOIN `articles_five` ac5 ON ac5.`created_by` = u.`id`
LEFT JOIN `articles_six` ac6 ON ac6.`created_by` = u.`id`
LEFT JOIN `articles` ae1 ON ae1.`edited_by` = u.`id`
LEFT JOIN `articles_two` ae2 ON ae2.`edited_by` = u.`id`
LEFT JOIN `articles_three` ae3 ON ae3.`edited_by` = u.`id`
LEFT JOIN `articles_four` ae4 ON ae4.`edited_by` = u.`id`
LEFT JOIN `articles_five` ae5 ON ae5.`edited_by` = u.`id`
LEFT JOIN `articles_six` ae6 ON ae6.`edited_by` = u.`id`
WHERE COALESCE(ac1.`created_by`,ac2.`created_by`,ac3.`created_by`,
ac4.`created_by`,ac5.`created_by`,ac6.`created_by`,
ae1.`edited_by`, ae2.`edited_by`, ae3.`edited_by`,
ae4.`edited_by`, ae5.`edited_by`, ae6.`edited_by`)
IS NULL;

Related

How to write a query to get unique elements from a relation

I have 2 entities with a many to many relation. Users - Roles_Users - Roles
How can write a query that returns to me all the users that only has exactly one role which is the role name "customer". I wrote something like this:
SELECT `users`.* FROM `users`
INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE roles.name not in ('admin' , 'sac', 'superadmin', 'customer_service' , 'supplier');
but it still brought to me users that has more than the role 'customer'.
I need the users that has ONLY the role of 'customer' and nothing else
This is how I would solve it:
SELECT `users`.*
FROM `users`
WHERE id IN
(
SELECT `roles_users`.`user_id`
FROM `roles_users`
INNER JOIN `roles`
ON `roles`.`id` = `roles_users`.`role_id`
GROUP BY `roles_users`.`user_id`
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Btw, there's no need to use all those backticks:
SELECT *
FROM users
WHERE id IN
(
SELECT roles_users.user_id
FROM roles_users
JOIN roles
ON roles.id = roles_users.role_id
GROUP BY roles_users.user_id
HAVING COUNT(*) = 1 -- only a single role
AND MAX(roles.name) = 'customer' -- and this role is 'customer'
)
Isn't this easier to read (and write)?
Compare role name directly,because using in can get you other non required values
SELECT `users`.* FROM `users` INNER JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id` WHERE roles.name ='customer'
Why not just look users with the specific role?
SELECT `users`.* FROM `users`
JOIN `roles_users` ON `roles_users`.`user_id` = `users`.`id`
JOIN `roles` ON `roles`.`id` = `roles_users`.`role_id`
WHERE `roles`.name = 'customer';
Use a NOT EXISTS subquery in the WHERE clause:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
WHERE r.name = 'customer'
AND NOT EXISTS (
SELECT *
FROM roles_users ru1
WHERE ru1.user_id = ru.user_id
AND ru1.role_id <> ru.role_id
)
The NOT EXISTS condition will ensure that no other roles are assigned to the user.
A shorter but slower solution could be:
SELECT u.*
FROM users u
INNER JOIN roles_users ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id
GROUP BY u.id
HAVING GROUP_CONCAT(r.name) = 'customer'

MYSQL: Handling Multiple LEFT JOINS

I have a query with one LEFT JOIN that works fine. When I add a second LEFT JOIN to a table with multiple records per field in the first table, however, I am getting the product of the results in the two tables ie books x publishers returned. How can I prevent this from happening?
SELECT a.*,b.*,p.*, group_concat(b.id as `bids`)
FROM authors `a`
LEFT JOIN books `b`
ON b.authorid = a.id
LEFT JOIN publishers `p`
on p.authorid = a.id
GROUP by a.id
EDIT:
Figured it out. The way to do this is to use subqueries as in this answer:
SELECT u.id
, u.account_balance
, g.grocery_visits
, f.fishmarket_visits
FROM users u
LEFT JOIN (
SELECT user_id, count(*) AS grocery_visits
FROM grocery
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN (
SELECT user_id, count(*) AS fishmarket_visits
FROM fishmarket
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY u.id;
If you do multiple LEFT Joins, your query will return a cartesian product of the results. To avoid this and get only one copy of fields you desire, do a subquery for each table you wish to join as below. Hope this helps someone in the future.
SELECT u.id
, u.account_balance
, g.grocery_visits
, f.fishmarket_visits
FROM users u
LEFT JOIN (
SELECT user_id, count(*) AS grocery_visits
FROM grocery
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN (
SELECT user_id, count(*) AS fishmarket_visits
FROM fishmarket
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY u.id;

Select last record mysql on left join

I have a table that stores all users connections ( date & ip ) and i want to retrieve with a single query all the users data (nickname , avatar ...) + the last record of my connections history table of this user ...
SELECT
*
FROM
`users`
LEFT JOIN
`connections_history` ON `users`.`id` = `connections_history`.`guid`
How i can proceed thx
Assuming that connections_history table has an AUTO_INCREMENT column id:
SELECT *
FROM (
SELECT u.*, MAX(h.id) as hid
FROM users u
LEFT JOIN connections_history h ON u.id = h.guid
GROUP BY u.id
) u
LEFT JOIN connections_history h ON h.id = u.hid
Unfortunately Mysql does not support window functions, you need Correlated sub-query to do this.
Try something like this
SELECT *
FROM users
LEFT JOIN connections_history ch
ON users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date)
One way is finding the rows with max date for each guid in subquery and then join with users table.
Like this:
select *
from `users` u
left join (
select *
from `connections_history` c
where date = (
select max(date)
from `connections_history` c2
where c.`guid` = c2.`guid`
)
) t on u.`id` = t.`guid`;
You can do this with a correlated subquery in the ON clause:
SELECT u.*, ch.*
FROM `users` u LEFT JOIN
`connections_history` ch
ON ch.`guid` = u.`id` AND
ch.date = (SELECT MAX(ch2.date)
FROM connections_history ch2
WHERE ch.guid = ch2.guid
);
This formulation allows the query to take advantage of an index on connections_history(guid, date).

mysql count from joins using 3 tables

i'm trying to build a chat application, in which i have these four tables
chat --id,group_id,body,time_posted[timestamp]
chat_groups --establish id,name for a group
users_groups --link users to a group, also define if user stared this group FIELDS::user_id,group_id,stared[bool]
wall_visit --user_id,group_id,last_visit[timestamp]
the idea is every users join a group, and they post to it in chat.
chat_groups table is just for defining the room, while users_groups is for setting access of members to this group.
wall_visit table is a table that store specific user last time accessed specific group (since its many to many u know..)
now what im trying to establish is to get in one query,
the chat_groups the user in relation with
the count of messages posted to this group since user last login (from settings)
the count of members in this group
the group name
:)
i have been trying to hours now :( best i could come up with
SELECT w.last_visit,access.stared,cg.user_id,u.fullname as username,cg.name as group_name ,cgu.count_members,c.count_msgs,c.time_posted
FROM `chat_groups`cg
inner join chat_groups_users access on (access.group_id = cg.id and access.user_id = ?)
left outer join users u on u.id = cg.user_id
left join wall_visit w on w.group_id = cg.id
left join (select count(*) as count_members,group_id from group_users group by group_id) cgu on cgu.group_id = cg.id
left join (SELECT count(wv.id) as count_msgs,c.group_id,c.time_posted FROM chats c
left outer join `wall_visit` wv on (wv.group_id is not null and c.group_id = wv.group_id and c.time_posted > wv.last_visit)
group by c.group_id) c on c.group_id = cg.id
where cg.user_id = 1
this query is working ..ehh, my main problem is with the count of the messages in the group since last_visit.
what is the best methode to get message_count to work :( ??
can this query be optimized more?
Thanks SO community :)
My 2nd attempt
SELECT w.last_visit,access.stared,cg.user_id,u.fullname as username,cg.with_id,uu.fullname as with_name,cg.name as group_name ,cgu.count_members,c.count_msgs,c.time_posted
FROM `chat_groups`cg
inner join chat_groups_users access on (access.group_id = cg.id and access.user_id = 1)
left outer join users u on u.id = cg.user_id
left join wall_visit w on w.chat_id = cg.id
left outer join users uu on uu.id = cg.with_id
left join (select count(*) as count_members,group_id from chat_groups_users group by group_id) cgu on cgu.group_id = cg.id
left join (
SELECT group_id,count(c.id) as count_msgs,time_posted FROM `chats` c inner join wall_Visit wv on wv.chat_id = c.group_id where c.id > wv.last_visit group by c.group_id
) c on c.group_id = cg.id
where cg.user_id = 1
this should fix you count message problem
SELECT
`cg`.`user_id`, `cg`.`with_id`, `cg`.`name` AS `group_name`,
`access`.`stared`,
`u`.`fullname` AS `username`,
`w`.`last_visit`,
`uu`.`fullname` AS `with_name`,
`cgu`.`count_members`,
`c`.`count_msgs`, `c`.`time_posted`
FROM `chat_groups` AS `cg`
INNER JOIN `chat_groups_users` AS `access`
ON (`access`.`group_id` = `cg`.`id` AND `access`.`user_id` = `cg`.`user_id`)
LEFT OUTER JOIN `users` AS `u`
ON (`u`.`id` = `cg`.`user_id`)
LEFT JOIN `wall_visit` AS `w`
ON (`w`.`chat_id` = `cg`.`id`)
LEFT OUTER JOIN `users` AS `uu`
ON (`uu`.`id` = `cg`.`with_id`)
LEFT JOIN (
SELECT COUNT(*) AS `count_members`, `group_id`
FROM `chat_groups_users`
GROUP BY
`group_id`
) AS `cgu`
ON (`cgu`.`group_id` = `cg`.`id`)
LEFT JOIN (
SELECT count(`c`.`id`) AS `count_msgs`, `c`.`time_posted`
FROM `chats` AS `c`
INNER JOIN `wall_visit` AS `wv`
ON (`wv`.`chat_id` = `c`.`group_id`)
WHERE
`c`.`time_posted` > `wv`.`last_visit`
GROUP BY
`c`.`group_id`
) AS `c`
ON (`c`.`group_id` = `cg`.`id`)
WHERE `cg`.`user_id` = 1
otherwise u have to setup a fiddle

chat application friend list

I have 3 MySQL tables namely chat_comments, chat_friends and user_details and I want to display a friend list.
My tables:
chat_comments(comment_id,comment,user_id,user_id2,date_added)
chat_friends(user_id,user_id2,approved)
user_details(user_id, mainimage_id, fullname)
To do this, I need a query that will return the needed fields (u.mainimage_id, u.fullname, b.comment, b.user_id) so I can loop through the list to display a table.
SQL so far (help from #Andriy M):
SELECT
cc.comment,
cc.date_added,
u.fullname,
u.mainimage_id
FROM
user_details u
LEFT JOIN
chat_comments cc
INNER JOIN (
SELECT
user_id,
MAX(comment_id) AS maxcomment
FROM chat_comments WHERE user_id=2020 OR user_id2=2020
GROUP BY user_id
) a ON a.user_id = cc.user_id
AND a.maxcomment = cc.comment_id
ON a.user_id = u.user_id
WHERE u.user_id IN (
SELECT user_id2
FROM chat_friends
WHERE user_id = 2020
AND approved = 1
)
The above query returns the last comment made by the logged-in user's friends in conversation not the last comment between the logged-in user and his/her friend regardless of who made it.
I would like it to return the last comment between the logged-in user and their friend individually regardless of who made it. In the chat_messages table, user_id is the sender and user_id2 is the receiver. Hope it makes sense?
Like #imm said in a comment, you need to use an outer join. In case of a left join, the user_details table should become the left side of the join, the right side being the result of your inner join of chat_comments with your a derived table. You'll also need to remove the user_id IN (…) condition from inside the a subselect and re-apply it to the user_details table. Here:
SELECT
cc.comment,
cc.date_added,
u.fullname,
u.mainimage_id
FROM
user_details u
LEFT JOIN
chat_comments cc
INNER JOIN (
SELECT
user_id,
MAX(comment_id) AS maxcomment
FROM chat_comments
GROUP BY user_id
) a ON a.user_id = cc.user_id
AND a.maxcomment = cc.comment_id
ON a.user_id = u.user_id
WHERE u.user_id IN (
SELECT user_id2
FROM chat_friends
WHERE user_id = 2020
AND approved = 1
)
;
Alternatively, you could use a right join. In this case you would just need to move the user_id IN (…) condition, similarly to the LEFT JOIN solution above, and replace the second INNER JOIN with RIGHT JOIN:
SELECT
cc.comment, cc.date_added, u.fullname, u.mainimage_id
FROM
(
SELECT user_id, MAX(comment_id) AS maxcomment
FROM chat_comments
GROUP BY user_id
) a
INNER JOIN
chat_comments cc ON
a.user_id = cc.user_id AND
a.maxcomment = cc.comment_id
RIGHT JOIN
user_details u ON
a.user_id = u.user_id
WHERE u.user_id IN (select user_id2 from chat_friends where user_id=2020 AND approved=1)