Get the rank of the row given the order in sql - php

Let's say I have two tables like the following:
user_id
1
2
3
post_id user_id
1 2
2 3
3 2
The first table has all of the user information and the second table is a list of posts from users.
With these table, I want to make a ranking of who post the most posts. To do this, I can use the following sql
select user.user_id from user
left join post on user.user_id=post.user_id
order by count(post.post_id)
which will give me
user_id
2
3
1
, but what if I only want a single user's rank? In other words, I want to write a sql statement that will return what place the user is in given the user_id. For example, I want 1 as output if I have user_id of 2, 2 as the output if I have user_id 3, and 3 as the output if I have user_id 1.
Is this possible, or would I have to select the entire table and do a while loop in php until I hit the user and count the rows above?

Unfortunately, for a single user's rank, you pretty much have to calculate the rank of everyone and then pull out the single user. So, the ranking for all users is:
select u.user_id, count(p.post_id), (#rn := #rn + 1) as ranking
from user u left join
post p
on u.user_id = p.user_id cross join
(select #rn := 0) params
group by u.user_id
order by count(p.post_id) desc;
And for one user, use a subquery:
select *
from (select u.user_id, count(p.post_id), (#rn := #rn + 1) as ranking
from user u left join
post p
on u.user_id = p.user_id cross join
(select #rn := 0) params
order by count(p.post_id) desc
) u
where u.user_id = $USERID;

You can use user-defined variables for this:
select rnk
from (
select user.user_id, #rnk:#rnk+1 rnk
from user
left join post on user.user_id=post.user_id
cross join (select #rnk:=0) t
group by user.user_id
order by count(post.post_id) desc
) t
where user_id = ?
BTW -- I believe your query was missing a group by clause. Added above.

Related

Assign and display ranks to users from mysql data [duplicate]

This question already has answers here:
Rank function in MySQL
(13 answers)
Closed 4 years ago.
SELECT u.user_id, u.user_uid, s.ostats, s.attack, s.defense
FROM stats s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
So in above data, "ostats"(overall) is just a sum of attack+defense and by using this query I could display users in descending order of their "ostats" values..
But how do I assign and display rank of each user, like the one with most "ostats" valued user as Rank 1 and the second highest "ostats" valued user as Rank 2 and so on..?
What about using a variable to keep track of the row number?
SET #rank = 0;
SELECT
u.user_id,
u.user_uid,
s.ostats,
s.attack,
s.defense,
(#rank:=#rank + 1) AS rank
FROM stats s
JOIN users u on s.id = u.user_id
ORDER BY s.ostats DESC;
You can assign a row number using variables:
SELECT u.user_id,u.user_uid, s.ostats, s.attack, s.defense,
s.ranking
FROM (SELECT s.*, (#rn := #rn + 1) as ranking
FROM (SELECT s.* FROM stats s ORDER BY s.ostats DESC) s CROSS JOIN
(SELECT #rn := 0) params
) s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
In the event of ties, this will give different users different rankings. If that is an issue, you can use this modified form:
SELECT u.user_id,u.user_uid, s.ostats, s.attack, s.defense,
s.ranking
FROM (SELECT s.*,
(#rn := if(#o = ostats, #rn,
if(#o := ostats, #rn + 1, #rn + 1)
)
) as ranking
FROM (SELECT s.* FROM stats s ORDER BY s.ostats DESC) s CROSS JOIN
(SELECT #rn := 0, #o := -1) params
) s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
Of course, in MySQL 8.0, you can use row_number(), rank() or dense_rank() for this purpose.

EXISTS query optimization on mysql query

I have a big data problem with MySQL.
I have:
a users table with 59033 rows, and
a user_notes table with 8753 rows.
But when I search which users have user note in some dates.
My query like this :
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
WHERE
EXISTS(
select * from user_notes
where user_notes.note_user_id = u.id AND user_notes.create_date
BETWEEN "2017-10-20" AND "2017-10-22"
)
ORDER BY u.lp_modify_date DESC, u.id DESC
Turn it around -- find the ids first; deal with the joins later.
SELECT u.*,
( SELECT rep.name
FROM users AS rep
WHERE rep.id = u.add_user ) AS rep_name
FROM (
SELECT DISTINCT note_user_id
FROM user_notes
WHERE create_date >= "2017-10-20"
AND create_date < "2017-10-20" + INTERVAL 3 DAY
) AS un
JOIN users AS u ON u.id = un.note_user_id
ORDER BY lp_modify_date DESC, id DESC
Notes
No GROUP BY needed;
2 tables seem to be unused; I removed them;
I changed the date range;
User notes needs INDEX(create_date, note_user_id);
Notice how I turned a LEFT JOIN into a subquery in the SELECT list.
If there can be multiple rep_names, then the original query is "wrong" in that the GROUP BY will pick a random name. My Answer can be 'fixed' by changing rep.name to one of these:
MAX(rep.name) -- deliver only one; arbitrarily the max
GROUP_CONCAT(rep.name) -- deliver a commalist of names
Rewriting your query to use a JOIN rather than an EXISTS check in the where should speed it up. If you then group the results by the user.id it should give you the same result:
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
JOIN user_notes AS un
ON un.note_user_id
AND un.create_date BETWEEN "2017-10-20" AND "2017-10-22"
GROUP BY u.id
ORDER BY u.lp_modify_date DESC, u.id DESC

get user rank according to the like number

I have 3 tables as below in MySQL:
user: id username ...
post: id user_id title ....
user_post_like: id post_id user_id like_date (every like in a separate row)
Every user can send and like posts.
Now I wanna get the specified user daily rank according to the number of likes(from user_post_like table) that the user's posts got in the last 24 hours using an SQL command.
Maybe using count command for counting the users that have more likes than the specified user.
select u.username, count(l.id) as likes
from user_post_like l
join `user` u on u.od = l.user_id
where now() - interval 24 hour >= l.like_date
group by u.id, u.username
order by count(l.id) desc
and if you need a rank number too, you can do
select u.username, count(l.id) as likes, #rank := #rank + 1 as rank
from user_post_like l
join `user` u on u.od = l.user_id
cross join (select #rank := 0) r
where now() - interval 24 hour >= l.like_date
group by u.id, u.username
order by count(l.id) desc

mysql multiple COUNT() from multiple tables with LEFT JOIN

I want to show the conclusion of all users.
I have 3 tables.
table post
post_id(index) user_id
1 1
2 3
3 3
4 4
table photo
photo_id(index) user_id
1 2
2 4
3 1
4 1
table video
photo_id(index) user_id
1 4
2 4
3 3
4 3
and in table user
user_id(index) user_name
1 mark
2 tommy
3 john
4 james
in fact, it has more than 4 rows for every tables.
I want the result like this.
id name post photo videos
1 mark 1 2 0
2 tommy 0 1 0
3 john 2 0 2
4 james 1 1 2
5 .. .. .. ..
Code below is SQL that can work correctly but very slow, I will be true appreciated if you help me how it using LEFT JOIN for it. Thanks.
SQL
"select user.*,
(select count(*) from post where post.userid = user.userid) postCount,
(select count(*) from photo where photo.userid = user.userid) photoCount,
(select count(*) from video where video .userid = user.userid) videoCount
from user order by user.id"
(or ORDER BY postCount, photoCount or videoCount ASC or DESC as i want )
I done researched before but no any helped me.
SELECT u.user_id,
u.user_name,
COUNT(DISTINCT p.post_id) AS `postCount`,
COUNT(DISTINCT ph.photo_id) AS `photoCount`,
COUNT(DISTINCT v.video_id) AS `videoCount`
FROM user u
LEFT JOIN post p
ON p.user_id = u.user_id
LEFT JOIN photo ph
ON ph.user_id = u.user_id
LEFT JOIN video v
ON v.user_id = u.user_id
GROUP BY u.user_id
ORDER BY postCount;
Live DEMO
Your method of doing this is quite reasonable. Here is your query:
select user.*,
(select count(*) from post where post.userid = user.userid) as postCount,
(select count(*) from photo where photo.userid = user.userid) as photoCount,
(select count(*) from video where video.userid = user.userid) as videoCount
from user
order by user.id;
For this query, you want the following indexes:
post(userid)
photo(userid)
video(userid)
user(id)
You probably already have the last one, because user.id is probably the primary key of the table.
Note that a left join approach is a bad idea in this case. The three tables -- posts, photos, and videos -- are independent of each other. If a user has five of each, then joining them together would produce 125 intermediate rows. If a user has fifty of each, it would be 125,000 -- a lot of extra processing.
Your answer is probably slow as it is using a correlated sub-query i.e. the sub query is running once for each user_id (unless the optimizer is doing something smart - which shouldn't be counted on).
You could use a left outer join and count or use something temporary like:
SELECT u.user_id,
u.user_name,
ph.user_count AS 'photoCount',
p.user_count AS 'postCount',
v.user_count AS 'videoCount'
FROM user u
INNER JOIN ( SELECT user_id,
COUNT(*) AS user_count
FROM photo
GROUP BY user_id
) ph
ON ph.user_id=u.user_id
INNER JOIN ( SELECT user_id,
COUNT(*) AS user_count
FROM post
GROUP BY user_id
) p
ON p.user_id=u.user_id
INNER JOIN ( SELECT user_id,
COUNT(*) AS user_count
FROM video
GROUP BY user_id
) v
ON v.user_id=u.user_id
There are pros and cons for both (depending on indexes). Always have a look at the query plan (using EXPLAIN for MySQL).

MySql inner join takes more than 10 seconds

I have two tables posts and followings
posts (id,userid,post,timestamp) 30 000 rows
and
followings(id_me,userid) 90 000 rows
I want to get lattest 10 posts form posts table based on the people i follow and my posts
SELECT p.*
FROM posts as p INNER JOIN
followings as f
ON (f.id_me=(my user id) AND p.userid=f.userid )
OR
p.userid=(my user id)
ORDER BY id DESC LIMIT 10
But it takes about 10-15 seconds to return. Thanks in advance!
First, remove the filter from the join clause, let the join just correlate the joining tables.
(
SELECT p.*
FROM posts as p
INNER JOIN followings as f ON p.userid=f.userid
where f.id_me=(my user id)
UNION
SELECT p.*
FROM posts as p
where p.userid=(my user id)
)
ORDER BY id DESC LIMIT 10
second, verify your indexes if that ids got no indexes it ill perform a full table scan for each cartesian product of both tables (30k x 90k =~ 3700k pairs being compared)
third, if you don't follow yourself you need a union from post you are following and your posts
Using an OR in SQL is a performance killer, try this:
SELEC p.*
FROM posts as p INNER JOIN
followings as f
ON (f.id_me=(my user id) AND p.userid IN (f.userid,(my user id)))
ORDER BY id DESC LIMIT 10
Do this query using union:
(SELECT p.*
FROM posts p INNER JOIN
followings f
ON (f.id_me=(my user id) AND p.userid=f.userid
)
union
(select p.*
from posts p
where p.userid=(my user id)
)
ORDER BY id DESC
LIMIT 10
If the two conditions never overlap, then use union all instead.
An OR condition like that prevents the query optimizer from making use of indexes. Use a UNION instead:
SELECT *
FROM (SELECT p.*
FROM posts as p
INNER JOIN followings as f
ON f.id_me=(my user id) AND p.userid=f.userid
UNION
SELECT *
FROM posts
WHERE userid = (my user id)) u
ORDER BY id DESC
LIMIT 10
It might be just me but I think your WHERE clause is in an inefficient location:
SELECT
p.*
FROM
posts p
INNER JOIN
followings f
ON p.userid=f.userid
WHERE
MyUserID IN (p.userid, f.id_me)
ORDER BY
id DESC
LIMIT
10
I read in comments that you have the required indexes. The problem is the query. Combining OR with a JOIN confuses the poor and (often) dumb optimizer. The LIMIT 10 should be helpful but the optimizer is not (yet) smart enough to make the best plan.
Try this query:
( SELECT p.*
FROM posts AS p
JOIN followings AS f
ON f.id_me = (my_user_id)
AND p.userid = f.userid
ORDER BY p.id DESC
LIMIT 10
)
UNION ALL
( SELECT p.*
FROM posts AS p
WHERE p.userid = (my_user_id)
ORDER BY p.id DESC
LIMIT 10
) AS x
ORDER BY id DESC
LIMIT 10 ;

Categories