業務背景
數據排名是很常用的功能,簡單的排名功能可以根據order by來實現,但是如果數據一樣,排名應該並列的時候,order by雖然是排序的,但是名次卻不是並列的。
我們先通過order by演示一下。
CREATE TABLE `user_score` ( `user_id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id', `score` TINYINT(3) UNSIGNED NOT NULL COMMENT '得分', PRIMARY KEY (`user_id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='用戶成績表'
插入數據
INSERT INTO user_score (score) VALUES (95), (94), (97), (95), (96), (96), (99), (98);
通過order by 排名
select * from user_score order by score desc;
可以看出,通過order by的結果雖然是有序的,但是不是真正的名次,此時如果要得到名次只有通過業務代碼中去排序得到1,2,3這樣的。
基本思路
我們通過order by可以得到排序后的結果,不過這個結果不代表名次,我們應該再進行一次遍歷來得到最終的名次。
這個遍歷的過程當然可以放到業務上去做,不過也可以通過sql直接就生成的。
思路也是一樣的,先order by獲取到了有序的數據,然后通過一個變量來計算真正的名次。
簡單的排名
SELECT u.user_id, u.score, @rank := @rank + 1 FROM (SELECT * FROM user_score ORDER BY score DESC) u, (SELECT @rank := 0) r;

這里我先通過order by得到了有序的結果,然后定義了一個變量@rank並賦值為0。
再次通過SELECT 語句查詢排序后的結果,每一條數據結果都加變量@rank++,這樣就有了一個不區分並列情況的名次了。
並列排名
並列排名分為兩種情況,一種是並列了就占位位置了,比如名次是:1,2,2,4… 因為有兩個第二名,所以就占了第三名的位置。另一種就是並列了不占位置,名次就是:1,2,3,4…
並列但不占位
再簡單排名的基礎上,多創建一個變量,用來記錄上一個人的分數,然后通過比較來判斷名次是否需要增加
SELECT u.user_id, u.score, CASE WHEN @last_score = u.score THEN @rank WHEN @last_score := u.score THEN @rank := @rank + 1 END AS rank FROM (SELECT * FROM user_score ORDER BY score DESC) u, (SELECT @rank := 0, @last_score := NULL) r;

並列要占位
將簡單排名和不占位的並列排名綜合一下就可以得到並列要占位的排名了。
按照並列且占位的規則來排名,那么96分應該是第四名,95分是第6名。
我們觀察之前兩次查詢的結果,可以發現,當分數和上一次一樣的時候取第一個分數的排名,當分數不一樣的時候,取簡單排名的名次。
SELECT t.user_id, t.score, t.rank FROM (SELECT u.user_id, u.score, @rank := @rank + 1, @last_rank := CASE WHEN @last_score = u.score THEN @last_rank WHEN @last_score := u.score THEN @rank END AS rank FROM (SELECT * FROM user_score ORDER BY score DESC) u, (SELECT @rank := 0, @last_score := NULL, @last_rank := 0) r ) t;

