Getting recent user likes in MySQL - mysql

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

Related

MySQL How to GROUP BY counted column?

I have two tables users, orders
each table has below column
users(table)
id
orders(table)
user_id
How can i get the number of users whose order count is 1,2,3,4 ....n?
Like this?
users count | order count
999 | 1
100 | 2
80 | 3
70 | 4
60 | 5
50 | 6
What I have been trying so far is
SELECT cnt.uid as u_cnt, cnt.ocnt as or_cnt
FROM (
SELECT u.id as uid, COUNT(o.id) as o_cnt
FROM users as u
INNER JOIN orders o on u.id = o.user_id
) as cnt;
GROUP BY or_cnt
BUT I get only 1 u_cnt and summed or_cnt
You need two levels of group by clauses here: First, you need to group by user and count the number of orders each user has. Then, you need to take that result, group by the number of orders and count how many users have such an order count.
The easiest way to achieve this is probably with a subquery, where both the inner and outer query have a group by clause:
SELECT cnt.ocnt as or_cnt, COUNT(*) as user_count
FROM (
SELECT u.id as uid, COUNT(o.id) as o_cnt
FROM users as u
INNER JOIN orders o on u.id = o.user_id
GROUP BY u.id -- This was missing in your original query
) as cnt
GROUP BY or_cnt
You can use two levels of aggregation. More importantly, you do not need a JOIN. All the information you need is in orders:
SELECT o_cnt, COUNT(*) as user_count
FROM (SELECT o.user_id, COUNT(*) as o_cnt
FROM orders o
GROUP BY o.user_id
) u
GROUP BY o_cnt
ORDER BY o_cnt;

Get the number of users that belong to a group and the number of orders that belong to the group through its users

I have a user_groups table, a users table and an orders table.
The relationship between them is this: users.group_id, orders.user_id.
I'm trying to get the amount of users that belong to each group, and the amount of orders that belong to each group through its users.
My code:
select user_groups.*, count(users.id) as user_count, count(orders.id) as order_count
from user_groups
left join users on user_groups.id=users.group_id
left join orders on users.id=orders.user_id
group by user_groups.id
Expected output:
id | user_count | order_count
1 | 5 | 67
2 | 1 | 1
Actual output:
The amount of orders should be 5, not 71:
Use count(distinct):
select ug.*, count(distinct u.id) as user_count, count(distinct o.id) as order_count
from user_groups ug left join
users u
on ug.id = u.group_id left join
orders o
on u.id = o.user_id
group by ug.id;
count(id) counts the number of non-NULL values. You apparently want to count the number of different values, which is what distinct does.

MySQL select top rows with same condition values

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;

Select rows in order of total count

I have a table of users which hold a a users id that they voted for like this:
uid | voted_for
1 | 3
2 | 3
3 | 1
What i'm aiming to do is order uid based on how many people have voted for that uid. But I have no idea how to do it.
So the end result would be:
uid | Total_Votes
3 | 2
1 | 1
2 | 0
Hope you can help explain the best way to structure the SQL for this.
Perhaps something like this will help joining the table on itself:
SELECT u.*, voted_for_cnt
FROM users u
LEFT JOIN (
SELECT voted_for, count(1) voted_for_cnt
FROM users
GROUP BY voted_for
) t ON u.uid = t.voted_for
ORDER BY t.voted_for_cnt DESC
SQL Fiddle Demo
This simple query will produce the output you requested:
select voted_for as uid, count(*) as total_votes
from users
group by 1
order by 2 desc
If you want all data about each user in the output, join users to itself:
select u.*, count(v.uid) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2,3,4,5 -- put as many numbers here as there are columns in the users table
order by total_votes desc
This second query will give a total_votes score of zero if no one voted for the user.
Alternatively, you can select only those columns you want:
select u.uid, u.name, count(v.uid) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2
order by 3 desc
```
To return only the winners, do this:
select u.uid, u.name, count(*) as total_votes
from users u
left join users v on v.voted_for = u.uid
group by 1,2
having count(*) = (
select max(c) from (
select count(*) as c from users group by voted_for))
order by 3 desc

Joining table issues

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