问题
I am attempting to store the rank of users based on a score, all it one table, and skipping ranks when there is a tie. For example:
ID Score Rank
2 23 1
4 17 2
1 17 2
5 10 4
3 2 5
Each time a user's score is updated, The rank for the entire table must also be updated, so after a score update, the following query is run:
SET @rank=0;
UPDATE users SET rank= @rank:= (@rank+1) ORDER BY score DESC;
But this doesn't support ties, or skipping rank numbers after ties, for that matter.
I want to achieve this re-ranking in as few queries as possible and with no joins (as they seem rather time consuming).
I was able to get the desired result by adding two columns - last_score and tie_build_up - with the following code:
SET @rank=0, @last_score = null, @tie_build_up = 0;
UPDATE users SET
rank= @rank:= if(@last_score = score, @rank, @rank+@tie_build_up+1),
tie_build_up= @tie_build_up:= if(@last_score = score, @tie_build_up+1, 0),
last_score= @last_score:= score, ORDER BY score DESC;
I don't want those extra columns, but I couldn't get the single query to work without them.
Any ideas?
Thanks.
回答1:
Here's an alternate solution: don't store ranks at all! :-)
You can calculate them on the fly.
Example:
SELECT id, (@next_rank := IF(@score <> score, 1, 0)) nr,
(@score := score) score, (@r := IF(@next_rank = 1, @r + 1, @r)) rank
FROM rank, (SELECT @r := 0) dummy1
ORDER BY score DESC;
Result:
+------+----+-------+------+
| id | nr | score | rank |
+------+----+-------+------+
| 2 | 1 | 23 | 1 |
| 4 | 1 | 17 | 2 |
| 1 | 0 | 17 | 2 |
| 5 | 1 | 10 | 3 |
| 3 | 1 | 2 | 4 |
+------+----+-------+------+
nr
here is aт auxilliary column that indicates whether we should assign next rank or not.
You can wrap this query in another select
and perform paging, for example.
SELECT id, score, rank
FROM (SELECT id, (@next_rank := IF(@score <> score, 1, 0)) nr,
(@score := score) score, (@r := IF(@next_rank = 1, @r + 1, @r)) rank
FROM rank, (SELECT @r := 0) dummy1
ORDER BY score DESC) t
WHERE rank > 1 and rank < 3;
Result:
+------+-------+------+
| id | score | rank |
+------+-------+------+
| 4 | 17 | 2 |
| 1 | 17 | 2 |
+------+-------+------+
CAUTION: since now rank
is a calculated column, you can't index it and efficiently page far into dataset (that is, "select records with ranks from 3000 to 3010"). But it's still good for "select top N ranks" (provided that you put a corresponding LIMIT
on a query)
回答2:
I'm sure you have a good reason for this design choice but I think you should leave the rank out of the database all together. Updating the entire table for every change in one user's score can cause very serious performance issues with almost any size table. I suggest you reconsider that choice. I would advice to simply sort the table by score and assign ranks in the application code.
回答3:
i calculated rank and position the following way:
to update AND get the values i needs, i first added them, added an additional 1 and subtracted the original value. that way i do not need any help-columns inside the table;
SET @rank=0;
SET @position=0;
SET @last_points=null;
UPDATE tip_invitation
set
rank = @rank:=if(@last_points = points, @rank, @rank + 1),
position = ((@last_points := points)-points) + (@position := @position+1)
where
tippgemeinschaft_id = 1 ORDER BY points DESC;
来源:https://stackoverflow.com/questions/8684045/simple-mysql-update-rank-with-ties