(首先原諒我最近新番看多了,起了一箇中二的名字)sql
最近在找實習,因此打算系統總結(複習)一下sql中常常遇到問題。不論是刷leetcode仍是牛客的sql題,有一個問題老是繞不開的,那就是排名問題。其實對於MySql8.0以上版原本說,排名問題已經很容易解決了。由於MySql8.0以後開始支持三個窗口函數,分別是rank()
,dense_rank()
以及row_number()
。這三個窗口函數對應了排名問題中最多見的三種狀況。而對於以前的版本,則須要模擬這幾個函數。函數
網上也有不少相關的文章,但實際上他們給出的代碼都沒法100%經過leetcode的樣例。因而就想在這裏從新總結一下sql中的排名問題。咱們以分數排名爲例,假設有一個數據表scores包括兩個字段id和score,須要對score進行排名。code
id | score |
---|---|
1 | 0 |
2 | 15 |
3 | 15 |
4 | 15 |
5 | 17 |
6 | 18 |
7 | 20 |
不一樣的排名需求適合不一樣的場景,但爲了方便比較,就不分開舉例了。對於排名的結果,通常會包括如下幾種狀況:leetcode
若是是這樣的排名需求,排名的結果應該是:字符串
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 5 |
4 | 15 | 6 |
1 | 0 | 7 |
窗口函數 row_number()table
SELECT id, score, row_number() over (order by score desc) as 'rank' FROM Scores;
模擬窗口函數ast
SET @curRank = 0; SELECT id, Score, (@currank := @currank + 1) As 'rank' From Scores ORDER BY score DESC;
自行模擬窗口函數的關鍵就在於要設置一個變量來保存當前的排名。class
若是是這樣的排名需求,排名的結果應該是:變量
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 4 |
4 | 15 | 4 |
1 | 0 | 5 |
使用窗口函數 dense_rank()select
SELECT id, score, dense_rank() over (order by score desc) as 'rank' FROM Scores;
使用變量模擬窗口函數
對於這種排名,網上給出的代碼大可能是這樣的:
SELECT tmp.score, @ranking := case when @lastscore = tmp.score then @ranking when @lastscore := tmp.score then @ranking +1 end as 'rank' FROM (select * from scores order by score desc) tmp, (select @ranking := 0, @lastscore := null) r;
這段代碼可能在通常的數據表中都沒問題,但在leetcode上是不能徹底ac的。主要有兩個問題:
這種作法的思想是:
可是當score爲0時,兩個when中的表達式都是False,因此什麼都不執行,致使0值的結果爲null。
針對這種狀況,能夠稍微改進一下:
SELECT tmp.score, @ranking := case when @lastscore = tmp.score then @ranking +0 when @lastscore := tmp.score then @ranking +1 else @ranking := @ranking + 1 end as 'rank' FROM (select * from scores order by score desc) tmp, (select @ranking := 0, @lastscore := null) r;
這樣的寫法是能夠經過leetcode的全部樣例的。
使用聯結模擬窗口函數
SET @currank := 0; Select os.id, r.Score, r.rank From Scores os LEFT JOIN (SELECT *, (@currank := @currank + 1) As 'rank' From (Select * From Scores Group BY Score ORDER BY score DESC ) AS ra ) AS r ON os.score = r.score ORDER BY r.score DESC;
這種查詢其實能夠看作兩步,第一步是使用Group BY分組,進行了一個無重複值的排名(其實就是第一種狀況),以後再把這個排名表Left Join到原始的表中,而後對組合表在進行一次排名。
這種狀況應該是最符合現實中分數排名的。排名結果以下:
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 4 |
4 | 15 | 4 |
1 | 0 | 7 |
使用窗口函數rank()
SELECT id, score, rank() over (order by score desc) as 'rank' FROM Scores;
使用變量模擬窗口函數
SELECT tmp2.id, tmp2.score, tmp2.ranking AS 'rank' FROM (SELECT tmp.*, @rownum := @rownum+1 AS rownum, @ranking := case when @lastscore = tmp.score then @ranking + 0 when @lastscore := tmp.score then @rownum + 0 else @rownum + 0 end as ranking FROM (select * from scores order by score desc) tmp, (select @rownum := 0, @lastscore := null, @ranking := 0) r ) AS tmp2
這裏的作法其實至關於第一種狀況和第二種狀況的結合,用兩個變量分別存儲排名和當前的行數。
以上就是遇到比較多的排名問題的解法啦。關於窗口函數,其實還有更多的用途,好比分組排名。後面可能專門寫一篇來介紹這幾個排名窗口函數。
最後慣例:
能力有限,若有錯漏,多多包涵,歡迎指正!