MySQL 查詢組內(nèi) TOP N

測試數(shù)據(jù)

id username subject score
1 張三 語言 78
2 張三 數(shù)學(xué) 96
3 張三 外語 73
4 張三 歷史 87
5 李四 語言 90
6 李四 數(shù)學(xué) 22
7 李四 外語 80
8 李四 歷史 89
9 王五 語言 83
10 王五 數(shù)學(xué) 85
11 王五 外語 79
12 王五 歷史 68
13 趙六 語言 88
14 趙六 數(shù)學(xué) 90
15 趙六 外語 93
16 趙六 歷史 79

查詢需求

查詢出各科成績的前2名

解決方案

MySQL 8 以前的版本

方法一:使用會話變量

這種方法的思路如下:

1. 組內(nèi)排序
2. 組內(nèi)排序后,按順序給組內(nèi)每條記錄添加 `rank` 值, `rank` 值是從1開始遞增的
3. 查詢 `rank <= N` 的記錄
set @current_subject = null;
set @current_score = null;
select id, username, subject, score
from (
         select id,
                username,
                subject,
                score,
                @score_rank := IF(@current_subject = subject, IF(@current_score = score, @score_rank, @score_rank + 1),
                                  1) AS score_rank,
                @current_subject := subject,
                @current_score := score
         from test_score
         order by subject, score desc) tmp_table
where score_rank <= 2;

核心語句

@score_rank := IF(@current_subject = subject, IF(@current_score = score, @score_rank, @score_rank + 1), 1) AS score_rank

首先組內(nèi)排序后的結(jié)果如下:

id username subject score
8 李四 歷史 89
4 張三 歷史 87
16 趙六 歷史 87
12 王五 歷史 68
15 趙六 外語 93
7 李四 外語 80
11 王五 外語 79
3 張三 外語 73
2 張三 數(shù)學(xué) 96
14 趙六 數(shù)學(xué) 90
10 王五 數(shù)學(xué) 85
6 李四 數(shù)學(xué) 22
5 李四 語言 90
13 趙六 語言 88
9 王五 語言 83
1 張三 語言 78

歷史科目 組的成績?yōu)槔?,展?score_rank 的計(jì)算過程

id username subject score 備注
8 李四 歷史 89 @current_subject 初始為 null, 與該行的 subject 不相同,所以 @score_rank 被賦值為 1
4 張三 歷史 87 @current_subject 此時(shí)已被賦值為 歷史, 與該行的 subject 相同,但在處理上一條數(shù)據(jù)時(shí) @current_score 已被賦值為 89,與該行的 score不相等,所以 @score_rank 被賦值為 @score_rank + 1 即值 2
16 趙六 歷史 87 @current_subject 此時(shí)已被賦值為 歷史, 與該行的 subject 相同,但在處理上一條數(shù)據(jù)時(shí) @current_score 已被賦值為 87,與該行的 score相等,所以 @score_rank 被賦值為 @score_rank 即值 2
12 王五 歷史 68 @current_subject 此時(shí)已被賦值為 歷史, 與該行的 subject 相同,但在處理上一條數(shù)據(jù)時(shí) @current_score 已被賦值為 89,與該行的 score不相等,所以 @score_rank 被賦值為 @score_rank + 1 即值 3

其他

set @current_subject = null;
set @current_score = null;

上面的兩行不是必須的,之所以加上,是為了避免在同一個(gè) session 中已經(jīng)使用了相同的變量并為其賦了值,從而可能導(dǎo)致查詢結(jié)果不正確的情況。

方法二:自連接

思路:

1. 組內(nèi)排序
2. 取出組內(nèi)的一條數(shù)據(jù)
    1. 如果同組內(nèi)沒有比當(dāng)前分?jǐn)?shù)大的數(shù)據(jù),則當(dāng)前數(shù)據(jù)就是最大的數(shù)據(jù)
    2. 如果同組內(nèi)有 1 條數(shù)據(jù)的分?jǐn)?shù)比當(dāng)前分?jǐn)?shù)大,則當(dāng)前數(shù)據(jù)是第 2  大
    3. 如果同組內(nèi)有 2 條數(shù)據(jù)的分?jǐn)?shù)比當(dāng)前分?jǐn)?shù)大,則當(dāng)前數(shù)據(jù)是第 3  大
    4. …………
3. 找出同組內(nèi)少于 2 條數(shù)據(jù)比當(dāng)前數(shù)據(jù)分?jǐn)?shù)大的數(shù)據(jù)
select t1.*
from test_score t1
         left join test_score t2 on t1.subject = t2.subject and t1.score < t2.score
group by t1.username, t1.subject, t1.score
having count(t2.id) < 2
order by t1.subject, t1.score desc;

count(t2.id) 就是有幾條數(shù)據(jù)的分?jǐn)?shù)比當(dāng)前數(shù)據(jù)大。

這種方法有個(gè)缺點(diǎn),就是不能正確處理分?jǐn)?shù)相同的數(shù)據(jù)。

MySQL 8

MySQL 8 已經(jīng)支持 row_number、rankdense_rankover函數(shù)。

使用 rank() 函數(shù)

select id, username, subject, score
from (select id, username, subject, score, rank() over (partition by subject order by score desc) rank_
      from test_score) tmp
where tmp.rank_ <= 2;

完美!

使用 row_number() 函數(shù)

select id, username, subject, score
from (select id, username, subject, score, row_number() over (partition by subject order by score desc) row_number_
      from test_score) tmp
where tmp.row_number_ <= 2;

這種方法也有不能正確處理分?jǐn)?shù)相同的數(shù)據(jù)的缺點(diǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。