I have 3 tables that I'm looking to join:
pictures
--------
id user_id link
users
-----
id name
votes
-----
id user_id picture_id
Want I want to do is find the total number of votes for every picture for the specific user logged in. Pretty much I loop every picture out and if the user has votes on the picture they can't vote on it again.
Desired output:
---------------
id user_id link user_name total_votes
1 5 [link] Sean 5
So far I have something like this:
SELECT
p.*, u.username, d.total_votes
FROM pictures p
LEFT JOIN users u
ON p.user_id = u.id
LEFT JOIN
(
select id, picture_id, count(id) as has_voted from votes
) d on d.picture_id = p.id
I get all the pictures but all the votes are being added up on the first record.
EDIT
Sorry for being so unclear
So this is every image in my database. Say I'm logged in as Sean (user_id 1) I want to show how many times I votes on each image.
user_id is who uploaded the image.
(Updated) Try:
Select p.id, p.user_id, p.link, u.name, count(v.id) As total_votes
from pictures p
join users u on p.user_id = u.id
left join votes v on p.id = v.picture_id and v.user_id = ?
group by p.id
Try this
Select p.id, p.user_id, p.link, u.name,(CASE count(v.id) WHEN NULL THEN 0 ELSE count(v.id) END ) as total_votes
from pictures p
join users u on p.user_id = u.id
join votes v on (v.user_id = u.id and v.picture_id = p.id)
where v.id is not null
group by p.id
Related
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 site where user like products, i need for each product to show the list of up to 10 users who recently liked the product ordered by created at desc and their avatars. I am trying to find an efficient way to do that, note that a product can have 1000's of likes, and to be efficient I only want to show the last 10 likes.
I have two tables
products
id, title, ....
likes
id, user_id, product_id, created_at
I would like to get up to 10 user ids who liked the product recently. from there I will do another query on the user ids to get their names and avatars, but how do I make this first query to get the user ids for each product ?
so result should be
product_id, liked by
1 12,23,45,67
2 13,4,5
3 1
etc
Have you tried running this query:
SELECT p.id AS product_id, u.username, u.avatar, u.id AS user_id FROM products AS p
LEFT JOIN likes AS l ON l.produc_id = p.product_id
LEFT JOIN users AS u ON u.id = l.user_id
WHERE p.id = {PRODUCT_ID} -- If you want it for a single product
ORDER BY l.created_at DESC
LIMIT 10
This will return you the list of 10 usernames and their avatars for a product all in one query.
You might need to adjust this query as you have not provided detailed explanation of your table and their relations.
Edit:
adding another sql example as per request:
SELECT l.product_id, p.name AS product_name, GROUP_CONCAT(l.user_id) AS listOfUsers, GROUP_CONCAT(u.username) AS username, GROUP_CONCAT(u.avatar) AS avatars FROM likes AS l
LEFT JOIN products AS p ON p.id = l.product_id
LEFT JOIN users AS u ON u.id = l.user_id
WHERE l.product_id IN(1,2,3)
ORDER BY l.created_at DESC
LIMIT 10
This should return something like
product_id | listOfUsers | usernames | avatars
1 | 1,2,3 | test1,test2,test3 | img1, img2, img3
2 | 4,5,6 | test4,test5,test6 | img4, img5, img6
Edit #2:
I think this is the query you were looking for:
SELECT p.id, GROUP_CONCAT(l.user_id) As userList, GROUP_CONCAT(l.username) AS usernameList, GROUP_CONCAT(l.avatar) AS avatarList
FROM products AS p
LEFT JOIN (
SELECT l.product_id, l.user_id, u.username, u.avatar
FROM likes AS l
LEFT JOIN users AS u ON u.id = l.user_id
ORDER BY l.created_at DESC
LIMIT 10
)
AS l ON l.product_id = p.id
WHERE p.id IN (1,2,3)
GROUP BY p.id
I don't know how to title this problem. Correct me if you have better words.
I have two tables, Users and Posts.
Users:
id | username | password | ...
Posts:
id | author_id | title | content | ...
Now I want to list the "most active" users - the users who have written the most posts. And specifically, I want the top 10 result.
SELECT u.username, COUNT(p.id) AS count
FROM Posts p, Users u
WHERE u.id=p.author_id
GROUP BY p.author_id
ORDER BY count DESC
LIMIT 10;
I can get the expected result. However, the ranking may not be "fair" if some users have same number of posts.
E.g., I may get results like:
User 1 | 14
User 2 | 13
...
User 9 | 4
User 10 | 4
Here, there are actually several more users who have 4 posts.
So, the top 10 could be not exactly 10 results. How can I get a more "fair" result that contains extra rows of users who have 4 posts?
This is the right solution, I think: you need the subquery to know how much post has the 10th place in your top ten. Then, you use the outer query to extract the users with almost that postcount.
SELECT u.username, COUNT(p.id) AS count
FROM Posts p
JOIN Users u ON u.id = p.author_id
GROUP BY p.author_id
HAVING COUNT(p.id) >=
(
SELECT COUNT(p.id) AS count
FROM Posts p
JOIN Users u ON u.id = p.author_id
GROUP BY p.author_id
ORDER BY count DESC
LIMIT 9, 1
)
ORDER BY count DESC
Maybe not the best solution
select u.username, COUNT(p.id) AS count
FROM Posts p
join Users u on u.id = p.author_id
GROUP BY p.author_id
having COUNT(p.id) in
(
SELECT COUNT(p.id)
FROM Posts p
join Users u on u.id = p.author_id
GROUP BY p.author_id
ORDER BY count DESC
LIMIT 10
)
ORDER BY count DESC
Try this:
SELECT username, PostCount
FROM (SELECT username, PostCount, IF(#PostCount = #PostCount:=PostCount, #idx:=#idx+1, #Idx:=1) AS idx
FROM (SELECT u.username, COUNT(p.id) AS PostCount
FROM Posts p
INNER JOIN Users u ON u.id=p.author_id
GROUP BY p.author_id
) AS A, (SELECT #PostCount:=0, #Idx:=1) AS B
ORDER BY PostCount DESC
) AS A
WHERE idx <= 10;
I have 4 tables: posts, users, mentions, following
posts
----------------------------
id | user_id | post_text
1 1 foo
2 1 bar
3 2 hello
4 3 jason
users
------------
id | name
1 jason
2 nicole
3 frank
mentions
--------------------------
id | post_id | user_id
1 4 1
following
-------------------------------------------------
id | user_id | user_id_of_user_being_followed
1 1 2
posts includes the user_id of the user who posted some text
users has the user id and name of the user
mentions has the post id and user id of any post which has mentioned 1 or more other users
following has a the user id and the user they are following (user can follow 0 to many users)
What I'm trying to do is return all posts from users a that a given user follows, PLUS any posts that have mentioned that user (whether or not the given user is following), without returning any duplicates.
SELECT p.id, p.post, u.name,
FROM following f
JOIN posts p ON f.following = p.user_id
JOIN users u ON u.id = p.user_id
WHERE f.user_id = :user;
The above returns all posts from users that a given user is following, but I'm struggling figuring out how to include mentions as well (remember, a user does not have to follow someone to be able to see the post they've been mention in).
UPDATE:
Thanks to John R I was able to figure this out:
SELECT DISTINCT(p.id), p.post, u.name
FROM posts p
LEFT JOIN following f ON f.following = p.user_id
LEFT JOIN mentions m ON m.posts_id = p.id
JOIN users u ON u.id = p.user_id
WHERE (f.user_id = :user_id OR m.user_id = :user_id)
if i understand your querstion correctly you would want a left join to include any mentions.. but not filter out any followers/posts
if you can add some sample data to play with I can make sure its working how you want it to...
SELECT
if(p.id is not null, p.id, p1.id) as post_id,
if(p.post is not null, p.post, p1.post) as post_text,
u.username, m.id, m.user_id
FROM posts p
JOIN users u on u.id = p.user_id
JOIN following f on f.user_id_of_user_being_followed = u.id
LEFT JOIN mentions m on m.user_id = f.user_id
LEFT JOIN posts p1 on p1.id = m.post_id
WHERE f.user_id = :user or m.user_id = :user;
I left join mentions to the post made and also when the user_id in the mention table is equal to the specified user to filter out other users. the left join shouldn't change the number of rows returned.. but only include any mentions
EDIT: WORKING FIDDLE
after playing around with it I realised it was trying to put all of the data into one row.. try this:
(
SELECT p.id, p.post_text, u.name
FROM posts p
JOIN users u on u.id = p.user_id
JOIN following f on f.user_id_of_user_being_followed = u.id
WHERE f.user_id = 1
)
UNION
(
SELECT p.id, p.post_text, u.name
FROM following f
JOIN mentions m on m.user_id = f.user_id
JOIN posts p on p.id = m.post_id
join users u on u.id = p.user_id
WHERE f.user_id = 1
);
Maybe you inherited this db; but the last table is not really in line with good data normalization. The table should be the id and following_id; as set up you'll eventually run out of columns (or have to keep adding them when a user gets an error) - new users won't be able to follow anyone.
My database have got 4 table :
users
id
username
images
id
user_id
image
user_follow
user_id
user_follow
commentaries
image_id
text
I try to make a query to get all my and my friends pictures
1) row must be shown only if user have picture in "images" table
2) only image from me and my friends (depending on "user_follow" table)
3) count commentaries for each picture
my query is:
SELECT u.username as user, i.image as user_image, p.image, UNIX_TIMESTAMP(p.date) as date, COALESCE ( imgcount.cnt, 0 ) as comments
FROM users u
LEFT JOIN user_follow f ON u.id = f.follow_id
LEFT JOIN images p ON p.user_id = u.id
LEFT JOIN images i ON i.id = (SELECT b.id FROM images AS b where p.user_id = b.user_id ORDER BY b.id DESC LIMIT 1)
LEFT JOIN (SELECT image_id, COUNT(*) as cnt FROM commentaries GROUP BY image_id ) imgcount ON p.id = imgcount.image_id
WHERE f.user_id = 3 OR p.user_id = 3
ORDER BY p.date DESC
Commentaries count of each image sql line works fine in this query
LEFT JOIN (SELECT image_id, COUNT(*) as cnt FROM commentaries GROUP BY image_id ) imgcount ON p.id = imgcount.image_id
in this line I try to get user his last upload image as avatar from "images" table
LEFT JOIN images i ON i.id = (SELECT b.id FROM images AS b where p.user_id = b.user_id ORDER BY b.id DESC LIMIT 1)
This query dot not return result correctly because it show my friends who do not have pictures and something is not good with my own pictures (in database I have 2 row with my user_id : 3 ) but sql return 4
If I'm understanding correctly, it seems like you've made this a lot more complex than it needs to be. The reason you are getting records for your friends without pictures is because you are using a LEFT JOIN. Change that to an INNER JOIN and you will only get the records that match the join condition.
I think you are looking for something like the following. You'll need to make a few tweaks but hopefully this helps.
SELECT u.username as user, i.image as user_image, count(*) as comments
FROM users u
INNER JOIN user_follow f ON u.id = f.follow_id
INNER JOIN images p ON p.user_id = u.id
INNER JOIN commentaries c ON c.image_id = p.id
WHERE u.user_id = 3
GROUP BY u.username, i.image;