JCZ
JCZ

Reputation: 430

MySQL - select rank for users in a score table

I've got a 'user_score' table with that structure:

|id|user_id|group_id|score|     timestamp      |
| 1|      1|       1|  500| 2013-02-24 18:00:00|
| 2|      2|       1|  200| 2013-02-24 18:01:50|
| 3|      1|       2|  100| 2013-02-24 18:06:00|
| 4|      1|       1| 6000| 2013-02-24 18:07:30|

What I need to do is to select all users from that table which are from the exact group. Select their actual (according to timestamp) score in that group and their rank.

What I have is (edit: after Jocachin's comment I found out that my own query does not work as I expected, sorry to all):

SELECT user_id, score, @curRank := @curRank + 1 AS rank
FROM (
    SELECT * 
    FROM (
           SELECT * FROM `user_score`
           WHERE `group_id` = 1
           ORDER BY `timestamp` DESC
    ) AS sub2
    GROUP BY `user_id`
) AS sub, (SELECT @curRank := 0) r
ORDER BY `rank`

Expected result for example data and group_id = 1:

|user_id|score|rank|
|      1| 6000|   1|
|      2|  200|   2|

But MySQL subselects are a bit problematic, do you see any other solution, please?

I'll probably need to get the rank od single user in the group later. I am lost at the moment.

Upvotes: 3

Views: 9039

Answers (4)

zudtz
zudtz

Reputation: 1

I ran into a similar problem today and after unsuccessfully trying to implement the accepted answer I did this much simpler query

SELECT user_score.user_id, 
       MAX(user_score.score) AS score
FROM user_score 
WHERE user_score.group_id = 1 
GROUP BY user_score.user_id, 
         user_score.group_id
ORDER BY user_score.score DESC, 
         user_score.`timestamp` ASC

Upvotes: 0

Joachim Isaksson
Joachim Isaksson

Reputation: 180887

Although I'm not sure what "problematic" means in this context, here is the query rewritten as a plain LEFT JOIN with a subquery just to get the ranking right at the end (the ORDER BY needs to be done before the ranking);

SELECT user_id, score, @rank := @rank + 1 AS rank FROM
(
  SELECT u.user_id, u.score
  FROM user_score u
  LEFT JOIN user_score u2
    ON u.user_id=u2.user_id
   AND u.`timestamp` < u2.`timestamp`
  WHERE u2.`timestamp` IS NULL
  ORDER BY u.score DESC
) zz, (SELECT @rank := 0) z;

An SQLfiddle to test with.

EDIT: To take group_id into account, you'll need to extend the query somewhat;

SELECT user_id, score, @rank := @rank + 1 AS rank FROM
(
  SELECT u.user_id, u.score
  FROM user_score u
  LEFT JOIN user_score u2
    ON u.user_id=u2.user_id
   AND u.group_id = u2.group_id       -- u and u2 have the same group
   AND u.`timestamp` < u2.`timestamp`
  WHERE u2.`timestamp` IS NULL
    AND u.group_id = 1                -- ...and that group is group 1
  ORDER BY u.score DESC
) zz, (SELECT @rank := 0) z;

Another SQLfiddle.

Upvotes: 5

jurgenreza
jurgenreza

Reputation: 6086

I know an answer has been posted and accepted but I have a point about your original query that I think is worth mentioning.

When you group by, you are only allowed to select the columns you group by and those with aggregate functions such as MAX and COUNT. Selecting other columns is technically wrong.

This makes sense since it is not clear the data of which row should be returned. Mysql returns the data of the first row in group by; that is why your query works (because of the order by timestamp in the inner sub query). SQL server on the other hand raises an exception.

I think SQL Server approach is correct and this type of query should be avoided.

Upvotes: 1

Mikhail Vladimirov
Mikhail Vladimirov

Reputation: 13890

SELECT user_id, score, @rank := @rank + 1 AS rank 
FROM 
(
    SELECT 
        user_id AS user_id, 
        SUBSTRING_INDEX(GROUP_CONCAT(score ORDER BY timestamp DESC), ',', 1) 
            AS score 
    FROM user_score 
    WHERE group_id=1 
    GROUP BY user_id
) AS u, 
(
    SELECT @rank := 0
) AS r
ORDER BY score DESC;

Upvotes: 1

Related Questions