Update a MySQL table with record rankings within groups

人走茶凉 提交于 2019-12-02 06:28:09

问题


I have a table called 'winners' with columns 'id', 'category', 'score', 'rank'.

I need to update my table and asign a rank within the subcategories (category_id) which as per the sample is 2 but could be more than that in the future.

Most anwers I've found are based around select statements which simply tends to just output the table view but I did find a very good 'Update' answer (https://stackoverflow.com/a/2727239/4560380) specifically the answer update where ties are required to share the same rank.

Sample

CREATE TABLE winners (
id int,
category_id int,
score double,
rank int
);

INSERT INTO winners VALUES 
(1, 1, 4.36, NULL),
(2, 1, 2.35, NULL),
(3, 1, 1.25, NULL),
(4, 2, 4.21, NULL),
(5, 2, 3.33, NULL),
(6, 1, 4.24, NULL),
(7, 1, 1.22, NULL),
(8, 1, 1.25, NULL),
(9, 2, 4.21, NULL),
(10, 2, 3.63, NULL);

+----------+-------------+-------+------+
|       id | category_id | score | rank |
+----------+-------------+-------+------+
|        1 |           1 |  4.36 |      |
|        2 |           1 |  2.35 |      |
|        3 |           1 |  1.25 |      |
|        4 |           2 |  4.21 |      |
|        5 |           2 |  3.33 |      |
|        6 |           1 |  4.24 |      |
|        7 |           1 |  1.22 |      |
|        8 |           1 |  1.25 |      |
|        9 |           2 |  4.21 |      |
|       10 |           2 |  3.63 |      |
+----------+-------------+-------+------+

The linked answer above works perfectly for the data when there is only one category to worry about but not when there are multiple categories or subgroups to rank within.

I had attempted to add in a where clause to the code (line 8)

1. UPDATE   winners
2. JOIN     (SELECT  w.score,
3.                IF(@lastPoint <> w.score, 
4.                   @curRank := @curRank + 1, 
5.                   @curRank)  AS rank,
6.                @lastPoint := w.rating
7.      FROM      winners w
8.      WHERE category_id = 1
9.      JOIN      (SELECT @curRank := 0, @lastPoint := 0) r
10.     ORDER BY  w.score DESC
11.     ) ranks ON (ranks.score = winners.score)
12. SET winners.rank = ranks.rank;

with the intention of attempting to run the code twice for each category_id but the script fails.

Any options on modifying the answer above for multiple categories would be fantastic.

Needed result just to clarify (ranked within categories).

+----------+-------------+-------+------+
|       id | category_id | score | rank |
+----------+-------------+-------+------+
|        1 |           1 |  4.36 |    1 |
|        6 |           1 |  4.24 |    2 |
|        2 |           1 |  2.35 |    3 |
|        8 |           1 |  1.25 |    4 |
|        3 |           1 |  1.25 |    4 |
|        7 |           1 |  1.22 |    5 |
|        4 |           2 |  4.21 |    1 |
|        9 |           2 |  4.21 |    1 |
|       10 |           2 |  3.63 |    2 |
|        5 |           2 |  3.33 |    3 |
+----------+-------------+-------+------+

Thanks Guys!

UPDATE

Managed to find another bit of code https://stackoverflow.com/a/13270603/4560380 that I had somehow originally missed and was able to modifiy it with the where clause for each category_id succesfully. Not an ideal way of doing it - running multiple times for multiple categories but at this point in time it is fine.

set @currentRank = 0,
@lastRating = null,
@rowNumber = 1;
update winners r
inner join (
    select
        score,
        @currentRank := if(@lastRating = score, @currentRank, @rowNumber) rank,
        @rowNumber := @rowNumber + if(@lastRating = score, 0, 1) rowNumber,
        @lastRating := score
    from winners
    where category_id = 1
    order by score desc
) var on var.score = r.score
set r.rank = var.rank

further answers for a more 'automatic' handling of multiple categories within the ranking in 1 script run are still very welcome and appreciated.

Thanks

UPDATE

Just noticed that the answer that I found doesn't deal with zero scores (0.00) very well and places them ranked in the middle of other scores.

shawnt00 answer below is working and evaluates zero scores correctly. https://stackoverflow.com/a/34667112/4560380


回答1:


update winners
set rank = (
    select count(score) + 1
    from winners w2
    where w2.category_id = winners.category_id and w2.score > winners.score
)

count(*) will evaluate to zero even when no rows match the condition, i.e., the top ranking. If you wanted a dense rank you could do this:

update winners
set rank = (
    select count(distinct score) + 1
    from winners w2
    where w2.category_id = winners.category_id and w2.score > winners.score
)

EDIT: Per your comment I've found a query that does work. (It works on SQL Server and I'm not familiar with MySQL's quirks.) http://sqlfiddle.com/#!9/1159f/1

update winners
inner join (
    select w.id, w.category_id, count(w2.score) + 1 rank
    from winners w left outer join winners w2
        on w2.category_id = w.category_id and w2.score > w.score
    group by w.id
) r
    on r.id = winners.id
set winners.rank = r.rank


来源:https://stackoverflow.com/questions/34667070/update-a-mysql-table-with-record-rankings-within-groups

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