Count associated rows from multiple tables using left join - mysql

There are three tables. user, like, comment. Table like and comment has rows associated to user. I need all users with their associated row count from table like and comment. It's easy to do when there is only one table associated. However, here is my query.
SELECT u.id as id, u.display_name as displayName,
COUNT(x.user_id) as likeCount,
COUNT(y.user_id) as commentCount
FROM `user` u
LEFT JOIN
`like` x ON x.user_id = u.id
LEFT JOIN
`comment` y ON y.user_id = u.id
GROUP BY u.id
Table relationships:
One user has many likes
One user has many comments
commentCount is giving correct rows count, but likeCount giving wrong rows count. Please don't post answer which uses sub queries. I want it with only ONE SELECT clause. I am using MySQL. TIA

You can get the user count per individual table, like this:
SELECT user, COUNT(*) AS t1Count
FROM table1
GROUP BY user;
SELECT user, COUNT(*) AS t2Count
FROM table2
GROUP BY user;
Then you can join those two to the Users table to get the count of each. You should use COALESCE() to return null values with 0:
SELECT u.id, COALESCE(t1.t1Count, 0), COALESCE(t2.t2Count, 0)
FROM users u
LEFT JOIN(
SELECT user, COUNT(*) AS t1Count
FROM table1
GROUP BY user) t1 ON u.id = t1.user
LEFT JOIN(
SELECT user, COUNT(*) AS t2Count
FROM table2
GROUP BY user) t2 ON u.id = t2.user;
Here is an SQL Fiddle example.

Related

SQL Merge Two SELECT statements together

I have the following SQL query which returns 2 results which are the same only different by a where clause.
I would like to have them combined and group them under the user names. At the moment the two results are displayed but the results are not fully combined and the users are shown twice because of the two select statements.
How can I combine the two to create a single result.
The SQL query is as follows
SELECT
COUNT (asings.user_id),
post_status.status_id as status_id,
users.name,
users.id as user_id
from asigns
LEFT JOIN
post_status on asigns.post_id = post_status.post_id
RIGHT JOIN
users on asigns.user_id = users.id
WHERE
post_status.status_id = 2
GROUP BY users.id
UNION
SELECT
COUNT(asigns.user_id),
post_status.status_id as status_id,
users.name,
users.id from asigns
LEFT JOIN
post_status on asigns.post_id = post_status.post_id
RIGHT JOIN
users on asigns.user_id = users.id
WHERE
post_status.status_id = 3
GROUP BY users.id
See in this photo the usernames are appearing twice, what I want is to join the two username, and move count as two different columns depending on the status_id
How can I solve this. Thanks
You should use a single query with conditional aggregation:
SELECT
SUM(ps.status_id = 2) AS cnt_2,
SUM(ps.status_id = 3) AS cnt_3,
u.name,
u.id as user_id
FROM users u
LEFT JOIN assigns a ON a.user_id = u.id
LEFT JOIN post_status ps ON a.post_id = ps.post_id
GROUP BY u.id;
Use SELECT distinct for the union:
SELECT distinct * FROM ([FIRST SELECT] UNION [SECOND SELECT])
Or GROUP BY username
SELECT * FROM ([FIRST SELECT] UNION [SECOND SELECT]) GROUP BY username
BUT I suggest to review your both selects, if it differs just in the where clause so use where clause with the operator OR and you handle it just by one SELECT
Although your are using a LEFT and a RIGHT join and implicitly an INNER join between users and asigns, the WHERE clause transforms all the joins to INNER joins, because you are picking only the matching rows from post_status.
So, you only need INNER joins and conditional aggregation:
SELECT COALESCE(SUM(ps.status_id = 2), 0) counter_2,
COALESCE(SUM(ps.status_id = 3), 0) counter_3,
u.name,
u.id AS user_id
FROM users u
INNER JOIN asigns a ON a.user_id = u.id
INNER JOIN post_status ps ON ps.post_id = a.post_id
WHERE ps.status_id IN (2, 3)
GROUP BY u.id;

How to get data from 3 tables with same user id?

I have 3 tables like so
Table 1: UserInfo
user_id userName
123 userOne
Table 2: Post
user_id postContent
123 This is test message
Table 3: LikePost
user_id likesPostId
123 This is test message
I would like to run a query to get total number of post likes, posts, and user information from those 3 tables.
I can do this for each one such as in Post table:
SELECT COUNT(*) FROM Post WHERE Post.user_id = '123'
and SELECT * FROM UserInfo WHERE UserInfo.user_id = '123'
Is anyone have better solution in just 1 query? Thank you so much!
Use a structured query (with subqueries) something like this.
SELECT u.user_id, u.userName, p.num postcount, l.num likecount
FROM UserInfo u
LEFT JOIN (
SELECT COUNT(*) num,
user_id
FROM Post
GROUP BY user_id
) p ON u.user_id = p.user_id
LEFT JOIN (
SELECT COUNT(*) num,
user_id
FROM LikePost
GROUP BY user_id
) l ON u.user_id = l.user_id
What's going on here? The two subqueries, for example
SELECT COUNT(*) num,
user_id
FROM LikePost
GROUP BY user_id
each generate a virtual table with either zero or one row per user_id, showing a count for each user_id. You then join those virtual tables to your UserInfo table.
Use LEFT JOIN because ordinary innner JOIN will suppress users that lack either posts or likes.
Try This
SELECT ui.userName,Count(p.*),
Count(lp.*) as TotalPostLikes
FROM UserInfo ui
INNER JOIN Post p on p.user_id=ui.user_id
INNER JOIN LikePost lp on lp.user_id=ui.user_id
WHERE ui.user_id = '123'
GROUP BY ui.userName
If you want to select Username, Post and Likes on post, try the following
SELECT ui.userName,p.postContent as PostContent,
(SELECT COUNT(lp.user_id) FROM LikePost lp
WHERE lp.user_id=ui.user_id) as Likes,
(SELECT COUNT(_p .user_id) FROM Post _p
WHERE _p .user_id=ui.user_id) as TotalPosts
FROM UserInfo ui
INNER JOIN Post p on p.user_id=ui.user_id
WHERE ui.user_id = '123'
Yes you can do it within one query using leftjoin on Post and LikePost like below
SELECT COUNT(*),User.userName FROM UserInfo as User
leftjoin Post as Post on Post.user_id = User.user_id
leftjoin LikePost as LikePost on LikePost.user_id = User.user_id
where Post.user_id = 123
group by Post.user_id

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

Mysql count and return just one row of data

I need to count the amount of users that have have answered all of those 3 profile_options (so they have at least 3 records in the profile_answers table).
SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3
The problem is that this query is return a table with rows for each user and how many they answered (in this case always 3). What I need is to return just one row that has the total number of users (so the sum of all rows of this example)
I know how to do it with another subquery but the problem is that I am running into "Mysql::Error: Too high level of nesting for select"
Is there a way to do this without the extra subquery?
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) sum_sub
Please give this query a shoot
SELECT COUNT(DISTINCT(u.id)) AS users_count
FROM users AS u
INNER JOIN (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a ON a.user_id = u.id
If you have lots of data in your tables, you will get a better/faster performance by using temporary tables like so
CREATE TEMPORARY TABLE a (KEY(user_id)) ENGINE = MEMORY
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3;
Then your final query will look like this
SELECT COUNT(DISTINCT(u.id)) as users_count
FROM a
INNER JOIN on a.user_id = u.id
Unless there is a need to join the users table you can go with this
SELECT COUNT(*) AS users_count
FROM (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a
Should you need another solution, please consider providing us you EXPLAIN EXTENDED for the query and the table definitions along with a better problem description.
I hope this helps
You can give the queries a name using the AS clause. See the updated query below.
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) as users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) as sum_sub
You should not group by on a field not present in select statement.
select id, count(*) from users group by id is fine
select count(id) from users group by id is NOT
Regarding your query I think the link to user table is not necessary. Just using foreign key should be fine.
Try this one:
select count(*) from
(SELECT users_id count(*) as cnt
FROM profile_answers
INNER JOIN users ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
group by users_id
having count(*) >3)

mySql Select last entry for each user

I have two tables:
users:
user_id user_name
data:
user_id user_data user_time
I wan to select the latest entry from the data table, but return the user_name, user_id, user_data and user_time.
I have tried the following query, but it returns the first entry, not the last for each user:
sql = "SELECT users.user_name, users.user_id, data.user_data, data.user_time
FROM users
INNER JOIN data ON data.user_id = users.user_id
GROUP BY users.user_name
ORDER BY data.user_time DESC";
Use GROUP BY and MAX, WHERE...IN:
SELECT u.user_id, u.user_name, d.user_data, d.user_time
FROM users u
INNER JOIN data d ON d.user_id = u.user_id
WHERE (d.user_id, d.user_time) =
(SELECT user_id, MAX(user_time) FROM data GROUP BY user_id)
I think you had better add data_id column to data table.
Unless data_id, both user_id and user_time are necessary for PRIMARY KEY(and user_time is not always unique, not reliable)
If there is data_id, it can be bitly simple:
SELECT u.user_id, u.user_name, d.user_data, d.user_time
FROM users u
INNER JOIN data d ON d.data_id =
(SELECT data_id FROM data
WHERE user_id = u.user_id ORDER BY data_time DESC LIMIT 1)