1. 程式人生 > >MySQL實現Oracle rank()排序

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 ;