MySQL實現Oracle rank()排序
一、Oracle寫法介紹
MySQL5.7版本沒有提供類似Oracle的分析函式,比如開窗函式over(...),oracle開窗函式over(...)使用的話一般是和order、partition by、row_number()、rank()、dense_rank()幾個函式一起使用,具體的用法可以參考我之前的部落格oracle開窗函式用法簡介
假如要獲取成績排序第一的學生資訊,可以用如下的SQL:
select * from (select stuId, stuName, classId, row_number() over(partition by classId order by score desc) rn from t_score) where rn = 1;
二、Oracle和MySQL寫法對比
ok,就用學生成績排名的例子
學號 | 姓名 | 班級 | 成績 |
---|---|---|---|
111 | 小王 | 1 | 92 |
123 | 小李 | 2 | 90 |
134 | 小錢 | 3 | 92 |
145 | 小順 | 4 | 100 |
資料表為t_score,欄位分別為stuId,stuName,classId ,score
環境準備,先建表,寫資料:
#成績表 CREATE TABLE t_score( stuId VARCHAR(20), stuName VARCHAR(50), classId INT, score FLOAT ); # 寫資料 INSERT INTO t_score(stuId,stuName,classId,score) VALUES('111','小王',2,92); INSERT INTO t_score(stuId,stuName,classId,score) VALUES('123','小李',1,90); INSERT INTO t_score(stuId,stuName,classId,score) VALUES('134','小錢',1,92); INSERT INTO t_score(stuId,stuName,classId,score) VALUES('145','小順',2,100);
然後給出sql,用的是臨時變數的方法:
SELECT IF( @classId := c.classId, @rn := @rn + 1, @rn := 1 ) AS rn, c.stuId, c.stuName, c.classId, c.score , @classId := c.classId FROM (SELECT stuId, stuName, classId, score FROM t_score ORDER BY score ASC) c, (SELECT @rn := 0, @classId := NULL) r ;
不過對於上面的寫法,這裡也進行分析,讓學習者可以更好理解,因為很多地方都是直接貼程式碼,不寫明原因,對於入門者來說,可能都不理解
用執行計劃來解釋:
加上Explain,對於執行計劃不熟悉的學習者可以參考我之前部落格:MySQL Explain學習筆記
從執行計劃可以看出:
- ①、上面SQL,執行時候是先執行這條衍生查詢SQL,
SELECT @rn := 0,@classId := NULL
,這個其實是為了初始化臨時變數@rn和@classId - ②、執行查詢t_score,
SELECT stuId, stuName,classId,score FROM t_score ORDER BY score ASC
,同樣是返回一個衍生表 - ③、主查詢1,
SELECT @rn := 0,@classId := NULL
衍生查詢完成後,進行別名為r的主查詢
- ④、同理,主查詢2,衍生查詢
SELECT stuId, stuName,classId,score FROM t_score ORDER BY score ASC
查詢成功後,在進行外面的主查詢,也就是對別名為c的主表查詢
所以網上這種寫法也是值得學習的,一種是利用了mysql的執行計劃執行順序對臨時變數進行賦值,然後再用臨時變數進行疊加,寫法還是值得學習的
對於臨時變數的知識點,可以參考我之前部落格:MySQL變數學習筆記
注意:這裡網上有很多這種寫法,不過我驗證了,並不能實現了oracle類似的partition by效果,也就是沒分組效果,也有可能是哪裡寫錯了,歡迎指出!
MySQL實現的效果:
Oracle實現的效果:
很顯然,如圖如比對所示,在oracle裡,不僅分組了,而且rn也按照班級進行排名,Oracle實現的效果顯然和網上很多地方提出的這種寫法效果是不一樣的,網上的這種寫法僅僅是進行排序而已,並沒有按照班級進行分組排名
上面都是自己動手驗證過,目的是指出網上很多地方的這種寫法是不正確的,或許也有可能是自己寫錯哪裡了,都歡迎指出!
所以,對於Oracle rank()、row_number加上開窗函式進行排序,並沒有partition by分組的時候,是可以用這種方法,不過寫法要改一下,程式碼如:
SELECT
/* IF(
@classId := c.classId
AND @score := c.score,
@rn := @rn + 1,
@rn := 1
) AS rn,*/
rn := @rn+1 as rn,
c.stuId,
c.stuName,
c.classId,
c.score ,
@classId := c.classId
FROM
(SELECT
stuId,
stuName,
classId,
score
FROM
t_score
ORDER BY score ASC,classId) c,
(SELECT
@rn := 0,
@classId := NULL) r ;