Left join with group_concat is too slow - mysql

I have 2 tables:
users (id, firstname, lastname, etc)
users_to_groups (user_id(index), group_id(index))
I would like to make a query that returns records like the following:
firstname lastname groups
John Smith 1,2,3,5
Tom Doe 3,5
I use the GROUP_CONCAT function, and currently my query is:
SELECT * FROM users
LEFT OUTER JOIN
(
SELECT user_id, group_concat(group_id) FROM users_to_groups GROUP BY user_id
) AS i
ON users.id = i.user_id
It works, but it's very slow. I have 40k users and 260k records in the groups table.
Looks like the query doesn't use the index and goes through all the 260k lines for every user.
Is there any way to make it faster? It takes 3+ minutes, but I think it shouldn't.
Thanks!

try:
SELECT
u.user_id, u.firstname, u.lastname, group_concat(g.group_id)
FROM users u
LEFT OUTER JOIN users_to_groups g ON u.id on g.user_id
GROUP BY u.id, u.firstname, u.lastname

It's not the left join, but the sub select that makes your query slow. MySQL really suck when it comes to sub select.
This is probably faster:
SELECT
u.id, u.firstname, u.lastname,
group_concat(ug.group_id) AS groups
FROM
users u
LEFT JOIN users_to_groups ug ON ug.user_id = u.id
GROUP BY
u.id, u.firstname, u.lastname

Related

MySQL complex select query filtering from third table

I have three tables.
users, friends and communityusers tables.
I am trying to build a facebook like group system. User will try to search his/her friends from friend lists to add them to the group.
So here I am trying to remove those friends who are already added in the group. Here group users table is communityusers..
This is the structure of my communityusers table.
id | community_id | user_id
I am not being able to use this table with my current query.
SELECT f.id, u.id as user_id, u.userName, u.firstName, u.lastName, u.profilePic from friends f, users u
WHERE CASE
WHEN f.following_id=1
THEN f.follower_id = u.id
WHEN f.follower_id=1
THEN f.following_id = u.id
END
AND
f.status= 2
AND
(
u.firstName LIKE 's%' OR u.lastName LIKE 's%'
)
This query returns the friends lists of user id 1 now I want exclude users filtering from the communityusers table.
user_id from this query shouldn't be present in the communityusers table. How can I filter this from communityusers or third table?
Please let me know if question is not clear.
Any suggestion, idea and help would be highly appreciated.
Thank you so much.
Edit
After running the query I get
My communityusers table
See user_id = 2 is also selected from the above query. I want to remove the user_id = 2 from the result since it exists in the communityusers table.
I am trying to create a Fiddle but for some reason it's not working for me.
Thank you again.
#sadek
Simply use not in, in where condition
SELECT f.id, u.id as user_id, u.userName, u.firstName, u.lastName, u.profilePic
from friends f inner join users u on
CASE WHEN f.following_id=1 THEN f.follower_id = u.id WHEN f.follower_id=1 THEN f.following_id = u.id
END AND f.status= 2
where u.id not in (select distinct user_id from communityusers) and (u.firstName LIKE 's%' OR u.lastName LIKE 's%')
Try using left join with communityuser table by given condition cu.user_id is null - this will give you those users which are not in communituuser table
select a.* from
(SELECT f.id, u.id as user_id, u.userName, u.firstName, u.lastName, u.profilePic from friends f inner join users u
on CASE WHEN f.following_id=1 THEN f.follower_id = u.id WHEN f.follower_id=1 THEN f.following_id = u.id
END AND f.status= 2 where u.firstName LIKE 's%' OR u.lastName LIKE 's%')a
left join (select * from communityuser where community_id<>4) cu on a.user_id=cu.user_id
where cu.user_id is null

Count different totals from multiple tables in mysql grouped by user_id in one query

I want to count user_id from courses_taken and quiz_attempts table but my query brings me wrong numbers.
SELECT
u.id,
u.email,
u.user,
u.joined,
MAX(qa.last_attempt_time) as last_attempt_time,
COUNT(qa.user_id) total_quiz,
COUNT(ct.user_id) total_courses
FROM users u
LEFT JOIN courses_taken ct
ON u.id = ct.user_id
LEFT JOIN quiz_attempt qa
ON u.id = qa.user_id AND qa.attempt_mode=1
GROUP BY u.id
ORDER BY total_courses DESC
Table structure
users table
id, email, user, joined
quiz_attempt table
id,user_id, last_attempt_time, attempt_mode etc.
courses_taken table
id,user_id,course_id,taken_on etc.
Here i am trying to get all users with their total number of quiz attempts and total number of courses taken. But my query returns same numbers for both quiz attempts and courses taken.
What you can do is use COUNT DISTINCT on a column which varies uniquely with the value that you are trying to count, i.e.:
...
COUNT(DISTINCT qa.id) total_quiz,
COUNT(DISTINCT ct.course_id) total_courses
...
SqlFiddle here
You should not put distinct on the user_ID column but put it on the id for that table like this:
SELECT u.id, u.email, u.userid, u.joined,
MAX(qa.last_attempt_time) as last_attempt_time,
COUNT(DISTINCT qa.id) as total_quiz,
COUNT(DISTINCT ct.id) as total_courses
FROM users u LEFT JOIN
courses_taken ct
ON u.id = ct.user_id LEFT JOIN
quiz_attempt qa
ON u.id = qa.user_id AND qa.attempt_mode = 1
GROUP BY u.id, u.email, u.userid, u.joined
ORDER BY total_courses DESC;
or if this confuses you, you can use subquery like this:-
SELECT
u.id,
u.email,
u.UserId,
u.joined,
qa.last_attempt_time as last_attempt_time,
qa.total_quizCOUNT,
ct.total_coursesCOUNT
FROM users u
LEFT JOIN
(Select user_id, Count(user_id) as total_coursesCOUNT from courses_taken group by user_id) ct
ON u.id = ct.user_id
LEFT JOIN (Select user_id, Count(user_id) total_quizCOUNT, MAX(last_attempt_time) as last_attempt_time from quiz_attempt where attempt_mode = 1 group by user_id) qa
ON u.id = qa.user_id
ORDER BY total_coursesCOUNT DESC
You probably have a cartesian product problem because of the join. The better solution is to pre-aggregate the results. However, in many cases if the tables are not too big, then count(distinct) solves the problem:
SELECT u.id, u.email, u.user, u.joined,
MAX(qa.last_attempt_time) as last_attempt_time,
COUNT(DISTINCT qa.id) as total_quiz,
COUNT(DISTINCT ct.id) as total_courses
FROM users u LEFT JOIN
courses_taken ct
ON u.id = ct.user_id LEFT JOIN
quiz_attempt qa
ON u.id = qa.user_id AND qa.attempt_mode = 1
GROUP BY u.id
ORDER BY total_courses DESC;
Note that this works because you are using MAX() and COUNT(). It would not work with SUM() or AVG().

How to get SUM of columns and Group By unique ID's of rows in Mysql?

This is my current query i am using, but I am not getting the
expected output.
SELECT u.user_id, u.email, SUM(ca.credits_added) as credits_added, SUM(cs.credits_spent) as credits_spent
FROM weg_whitehound.users u
JOIN weg_whitehound.credits_added ca
ON u.user_id = ca.user_id
JOIN weg_whitehound.credits_spent cs
ON u.user_id = cs.user_id
GROUP BY ca.user_id
and my Original Database is here
You will get every possible combination of credits added and credits spent for each user due to the joins.
You probably need to join sub queries:-
SELECT u.user_id, u.email, ca.credits_added, cs.credits_spent
FROM weg_whitehound.users u
JOIN (SELECT user_id, SUM(credits_added) as credits_added FROM weg_whitehound.credits_added GROUP BY user_id) ca
ON u.user_id = ca.user_id
JOIN (SELECT user_id, SUM(credits_spent) as credits_added FROM weg_whitehound.credits_spent GROUP BY user_id) cs
ON u.user_id = cs.user_id
GROUP BY u.user_id, u.email

mysql query returns only one results from my database

I have 2 tables in my database. Table users and table profile.
users(user_id, surname, email)
profile(profile_id, country, user_id)
user_id in table profile, is FK (comes from users table). I have the following query in order to select all surnames "smith" from my database, that are from country "USA". This is my query:
SELECT u.name,
u.surname,
u.email,
u.user_id,
p.user_id
FROM users u
INNER JOIN profile p ON p.country = 'USA'
WHERE u.surname = 'smith' AND u.user_id = p.user_id
this query works fine, but the problem is that returns only 1 result and not all results from my database (people with surname smith that are from USA). Any idea where it might be the wrong and how to correct it?
You should put the u.user_id = p.user_id condition with ON condition because you want to apply JOIN on 'user_id' field. And where clause should have the remaining condition.
SELECT u.name,
u.surname,
u.email,
u.user_id,
p.user_id
FROM users u
INNER JOIN profile p ON u.user_id = p.user_id
WHERE u.surname = 'smith' And p.country = 'USA'
You can read about INNER JOINS
Use only the columns in your join on condition that are relevant for the join. The columns that link 2 tables together. And put the rest in the where clause. That should work:
SELECT u.name,
u.surname,
u.email,
u.user_id,
p.user_id
FROM users u
INNER JOIN profile p ON u.user_id = p.user_id
WHERE p.country = 'USA'
AND u.surname = 'smith'
This is also better for readability. You can read it like this:
SELECT these columns from table users. JOIN also table profile ON this 2 columns. After that filter the data and return only the records WHERE these conditions are met.

Selecting rows that are grouped, even if they aren't in that other table

Titling SQL questions is hard! Feel free to change it if you can think how to describe this better!
I have a pretty typical set up: 3 tables, users, groups, and group_members.
Here's an SQL Fiddle: http://sqlfiddle.com/#!2/23712/1
What I want to know is which groups are which users in.
So, I'm running:
SELECT u.id, u.firstname, u.lastname,
GROUP_CONCAT(m.group_id) as groups
FROM group_members m, users u
WHERE m.user_id = u.id
GROUP BY id
ORDER BY u.lastname ASC
Which is cool, and shows me the users name and what groups they are in.
My problem is that users who aren't in any groups don't show up, as of course the WHERE bit doesn't match them.
How can I also return the users who aren't in any group? (In the SQL Fiddle above, I want another row for Zack Jones, showing that he is either in group 0, or NULL or something!)
You will want to use a LEFT JOIN of your users table on the group_members:
SELECT u.id,
u.firstname,
u.lastname,
GROUP_CONCAT(m.group_id) as groups
FROM users u
LEFT JOIN group_members m
ON u.id = m.user_id
GROUP BY u.id, u.firstname,
u.lastname
ORDER BY u.lastname ASC
see SQL Fiddle with Demo
Or you can RIGHT JOIN group_members to users
SELECT u.id,
u.firstname,
u.lastname,
GROUP_CONCAT(m.group_id) as groups
FROM group_members m
RIGHT JOIN users u
ON m.user_id = u.id
GROUP BY id, u.firstname,
u.lastname
ORDER BY u.lastname ASC
see SQL Fiddle with Demo
SELECT
u.id, u.firstname, u.lastname, GROUP_CONCAT(m.group_id) as groups
FROM
users u
LEFT JOIN
group_members m ON m.user_id = u.id
GROUP BY u.id, u.firstname, u.lastname
ORDER BY u.lastname ASC
Use explicit JOINs, not implied-WHERE JOINS generally. In this case, use LEFT OUTER JOIN
Also, don't rely on the MySQL GROUP BY extensions: they don't always work as expected
You should use a LEFT JOIN on the table where you are not sure if matches will available. In your case you should LEFT JOIN the group_members table.
SELECT u.id,
u.firstname,
u.lastname,
GROUP_CONCAT(m.group_id) AS groups
FROM users
LEFT JOIN group_members
ON users.id = group_members.user_id
GROUP BY id
ORDER BY users.lastname ASC
SELECT u.id, u.firstname, u.lastname,
GROUP_CONCAT(m.group_id) as groups
FROM users u
LEFT JOIN group_members m on m.user_id = u.id
GROUP BY u.id
ORDER BY u.lastname ASC
Outer Joins are useful in these cases. I've swapped users and group_members m around in order to do a left outer join. You should also look at avoiding doing JOINs in a where clause.
SELECT u.id, u.firstname, u.lastname, GROUP_CONCAT(m.group_id) as groups
FROM users u LEFT OUTER JOIN group_members m
ON m.user_id = u.id
GROUP BY id
ORDER BY u.lastname ASC
SELECT
u.id,
u.firstname,
u.lastname,
m.groups
FROM users u
LEFT JOIN (
SELECT user_id,
GROUP_CONCAT(m.group_id) as groups
FROM group_members
) as m on m.user_id = u.id
WHERE m.user_id = u.id
GROUP BY id
ORDER BY u.lastname ASC
In your query you are using a cartisian product. Avoid using your method as it will bring too many undesired results instead use joins much faster as they are