I have a Spring MVC blog with functionality for Post and Comment voting. I want to return the top 3 users based on number of votes they've received on all their posts and comments.
tables:
users u [id, username]
posts p [id, u.id]
comments c [id, p.id, u.id]
post_votes pv [p.id, u.id, type (1 or -1)]
comment_votes cv [c.id, u.id, type (1 or -1)]
The following statement gives me total votes per user by querying two separate voting tables and then adding the totals together:
SELECT
(SELECT SUM(type)
FROM posts_votes pv
JOIN posts p ON p.id = pv.post_id
JOIN users u ON u.id = p.user_id
WHERE u.id LIKE ?1)
+
(SELECT SUM(type)
FROM comments_votes cv
JOIN comments c ON c.id = cv.comment_id
JOIN users u ON u.id = c.user_id
WHERE u.id LIKE ?1)
That works fine with a WHERE clause per user id... But now I'm trying to find just the top 3 users that have the most votes and I'm having too much difficulty. This is what I have so far:
SELECT u.id, u.username, IFNULL(SUM(pv.type), 0) AS totalPostVotes
FROM posts_votes pv
JOIN posts p ON p.id = pv.post_id
JOIN users u ON u.id = p.user_id
GROUP BY u.id ORDER BY totalPostVotes DESC LIMIT 3
That above statement works by itself giving me: u.id, u.username, and totalPostVote in descending order. So does the one below for comments:
SELECT u.id, u.username, IFNULL(SUM(cv.type), 0) AS totalCommentVotes
FROM comment_votes cv
JOIN comments c ON c.id = cv.comment_id
JOIN users u ON u.id = c.user_id
GROUP BY u.id ORDER BY totalCommentVotes DESC LIMIT 3
Great! But I want that third column SUM result to be essentially "totalVotes" and contain the sum of both of those subqueries. Then I'll GROUP BY u.id ORDER BY totalVotes DESC LIMIT 3.
Something like this:
SELECT u.id, u.username, SUM(
(SELECT IFNULL(SUM(pv.type), 0) AS totalPostVotes
FROM posts_votes pv
JOIN posts p ON p.id = pv.post_id
JOIN users u ON u.id = p.user_id
GROUP BY u.id ORDER BY totalPostVotes DESC LIMIT 1)
+
(SELECT IFNULL(SUM(cv.type), 0) AS totalCommentVotes
FROM comments_votes cv
JOIN comments c ON c.id = cv.comment_id
JOIN users u ON u.id = c.user_id
GROUP BY u.id ORDER BY totalCommentVotes DESC LIMIT 1))
AS totalVotes from users u
GROUP BY u.id, u.username ORDER BY totalVotes DESC LIMIT 3
id | username | totalVotes
2 user2 11
1 user1 11
29 user29 11
What's happening is the result of totalVotes is indeed the correct vote count, 11, for the "top" user, but none of those users are the real top user, and the correct vote is being repeated 3 times in the guise of other users. I'm not even sure how users are being sorted at that point because they're not in an order I recognize.
The subqueries work separately (they give me the correct user) when I add SELECT "u.id, u.username " IFNULL(SUM()) but then if I run the whole block, I get the error "Operand should contain 1 column(s)" So I delete them and revert to only SELECT IFNULL(SUM())
I'm also noticing the subqueries are only allowed LIMIT 1. How would I get the top 3, then? Should I do a UNION somewhere or is "+" sufficient? This is rather confusing. Can someone please help me with this? Any help is appreciated. Thanks in advance!
Updated code, thank you Peter:
SELECT
u.username,
pv_sum.total AS postTotal,
cv_sum.total AS commentTotal,
IFNULL(pv_sum.total, 0) + IFNULL(cv_sum.total, 0) as totalVotes
FROM users u
LEFT JOIN (
SELECT p.user_id, IFNULL(SUM(pv.type), 0) AS total
FROM posts p
JOIN posts_votes pv ON pv.post_id = p.id
GROUP BY p.user_id
) pv_sum ON pv_sum.user_id = u.id
LEFT JOIN (
SELECT c.user_id, IFNULL(SUM(cv.type), 0) AS total
FROM comments c
JOIN comments_votes cv ON cv.comment_id = c.id
GROUP BY c.user_id
) cv_sum ON cv_sum.user_id = u.id
GROUP BY u.username, postTotal, commentTotal
ORDER BY totalVotes DESC LIMIT 3;
Don't place your subqueries in your SELECT-part, but join them on the users-table:
SELECT
u.username,
pv_sum.total AS postTotal,
cv_sum.total as commentTotal,
IFNULL(pv_sum.total, 0) + IFNULL(cv_sum.total, 0) as totalVotes
FROM users u
LEFT JOIN (
SELECT p.user_id, IFNULL(SUM(pv.type), 0) AS total
FROM posts p
JOIN post_votes pv ON pv.post_id = p.id
GROUP BY p.user_id
) pv_sum ON pv_sum.user_id = u.id
LEFT JOIN (
SELECT c.user_id, IFNULL(SUM(cv.type), 0) AS total
FROM comments c
JOIN comment_votes cv ON cv.comment_id = c.id
GROUP BY c.user_id
) cv_sum ON cv_sum.user_id = u.id
GROUP BY u.id
ORDER BY totalVotes DESC
LIMIT 3;
Fiddle: http://sqlfiddle.com/#!9/980cb2/11
I have a select to get alerts.
select 'comments' prefix, c.foto, c.data as data, c.user, concat(k.user, ' comments your post') as logs from comentarios c
inner join posts p on c.foto = p.id
inner join cadastro k on c.user = k.id
where p.user = 1 and p.user <> c.user and c.delete = 0
union all
select 'like' prefix, l.post, l.data as data, l.user, concat(k.user, ' liked your post') as logs from likes l
inner join posts p on l.post = p.id
inner join cadastro k on l.user = k.id
where p.user = 1 and l.user <> p.user
order by data desc
limit 5
The problem is if I run the select this way it will take 2.4871 seconds.
If I remove the select 'like'... = 0.0024, but if I run this select apart it will take only 0.010 without the order by data desc.
any ideas why? I don't know if the problem is the union or the order by...
they are all well indexed.
i have query
SELECT P.*, COUNT(L.to) AS likes, U.name AS ownerName, U.username AS ownerUsername,
U.picture AS ownerPicture
FROM sn_posts P LEFT JOIN sn_users AS U
ON U.id = P.ownerID
LEFT JOIN sn_likes AS L
ON L.to = P.id
WHERE (P.ownerID = 69)
GROUP BY P.id
ORDER BY P.id DESC
it's taking - 0.3337 sec time,
Add an index for table sn_posts with ownerID field may
be nicer
Is it possible to do something like this:
SELECT
p.*, u.*
FROM
posts AS p
IF(p.status = 1)
LEFT JOIN users AS u
ON u.id = p.user_id
ELSE
LEFT JOIN pusers AS u
ON u.id = p.user_id
WHERE p.id = 10 ;
Based on post status being true/false join users/pusers table
No, but you can do this:
SELECT p.*,
(case when p.status = 1 then u.col1 else pu.col1 end) as col1
FROM posts p LEFT JOIN
users u
ON u.id = p.user_id and p.status = 1 LEFT JOIN
pusers AS pu
ON pu.id = p.user_id and p.status <> 1
WHERE p.id = 10 ;
In other words, you can join both tables and use the values from the table, based on the condition.
I've 3 tables to query. I make a select on the first one, depending on the two others. I must have only distinct id from the 1st table, but my query is returning some duplicates... http://sqlfiddle.com/#!2/3e3d6/1
My query:
SELECT p.*
FROM posts p, blogs_subscribed s
WHERE (p.user_id = s.user_id OR p.user_id = 1)
AND p.id NOT IN (
SELECT post_id
FROM posts_unsubscribed u
WHERE u.post_id = p.id
AND u.user_id = p.user_id);
SELECT p.*
FROM posts p, blogs_subscribed s
WHERE (p.user_id = s.user_id OR p.user_id = 1)
AND NOT EXISTS(
SELECT null
FROM posts_unsubscribed u
WHERE u.post_id = p.id
AND u.user_id = p.user_id);
Any idea please?
not entirely sure I understand what you are looking for, but I think this is what you want...
SELECT p.*
FROM posts p, blogs_subscribed s
WHERE (p.user_id = s.user_id OR p.user_id = 1)
AND p.id NOT IN (
SELECT post_id
FROM posts_unsubscribed u
WHERE u.post_id = p.id
AND u.user_id = p.user_id)
GROUP BY p.id;
SELECT p.*
FROM posts p, blogs_subscribed s
WHERE (p.user_id = s.user_id OR p.user_id = 1)
AND NOT EXISTS(
SELECT null
FROM posts_unsubscribed u
WHERE u.post_id = p.id
AND u.user_id = p.user_id)
GROUP BY p.id;