Simple MySQL Update Rank with Ties

做~自己de王妃 提交于 2019-12-19 04:34:07

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!