測試數(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
、rank
、dense_rank
、over
函數(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)。