一、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 ;