1 分組排序查詢
1.1 引言
排名是數據庫中的一個經典題目,實際上又根據排名的具體細節可分為3種場景:
連續排名
:例如薪水3000、2000、2000、1000排名結果為1-2-3-4,體現同薪不同名,排名類似於編號同薪同名但總排名不連續
:例如同樣的薪水分布,排名結果為1-2-2-4同薪同名且總排名連續
,同樣的薪水排名結果為1-2-2-3
值得一提的是:在Oracle
等數據庫中有窗口函數,可非常容易實現這些需求,而MySQL
直到8.0版本也引入相關函數
1.2 子查詢
1.2.1 方法一
排名第N的記錄 意味着該表中存在N-1
個比其更高的記錄
注意這里的N-1
個更高的薪水是指去重后的N-1
個,實際對應人數可能不止N-1個
最后返回的記錄也應該去重,因為可能不止一個記錄排名第N
由於對於每個記錄的where條件都要執行一遍子查詢,注定其效率低下
select a.*
from
(
select t1.*,(select count(*)+1 from 表 where 分組字段=t1.分組字段 and 排序字段<t1.排序字段) as group_id
from 表 t1
) a
where a.group_id<=3
1.2.1.1 方法分析
在查詢列中使用嵌套子查詢,在嵌套子查詢中用要分組的字段作為關聯字段,采用內關聯,分組字段不是唯一的,查出就會翻倍,再用排序字段錯位比較大小,就可以得出條數。但是
把條數查詢不是放在列中而是放在where
后就只會有一個條數,而不會每行都有條數,如下:(假定只查詢部門編號是20)
把求行數子查詢放在where后面
select * from employee e1 ,employee e2 where e1.deptno=e2.deptno
and e1.sal<e2.sal and e1.deptno = '20'
再對其求行數處理:(假定只查詢部門編號是20)
把求行數子查詢放在where后面
select count(*) from employee e1 ,employee e2 where e1.deptno=e2.deptno
and e1.sal<e2.sal and e1.deptno = '20'
要想能查出前三行,並且每行都顯示行數,就必須這樣處理:(假定只查詢部門編號是20)
select t1.*,(select count(*)+1 from employee where deptno=t1.deptno and sal<t1.sal) as group_id
from employee t1 where t1.deptno = '20'
缺點:如果一個部門下存在同樣的工資金額,那么就會有兩個一樣的行號了
1.2.2 方法二
和方法一差不多,但是沒有把排序的行放在記錄中
SELECT
*
FROM
employee tpn
WHERE
(
SELECT
COUNT(*)
FROM
employee t
WHERE
tpn.deptno = t.deptno AND tpn.sal <= t.sal
) < 3
1.2.3 方法三
此處的建表語句是模擬按照每位學生分組后,成績排名前三的要去
CREATE TABLE `score` (
`id` int NOT NULL AUTO_INCREMENT,
`stu_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`subject_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`score` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
插入語句
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (1, '1', '語文', 86);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (2, '1', '英語', 88);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (3, '1', '數學', 93);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (4, '1', '體育', 80);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (5, '2', '語文', 83);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (6, '2', '數學', 87);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (7, '2', '英語', 79);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (8, '2', '體育', 90);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (9, '3', '語文', 86);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (10, '3', '數學', 83);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (11, '3', '英語', 81);
INSERT INTO `score`(`id`, `stu_id`, `subject_name`, `score`) VALUES (12, '3', '體育', 86);
此處是SQL還是用了子查詢語句的分數來進行排序,同時要注意在子查詢語句中使用limit
語法的話,需要在嵌套一層,不然會提示語法錯誤
select * from
score a
where score in (
select score from (
select score from score s where s.stu_id = a.stu_id order by score desc limit 3
) a
)
1.3 自定義變量
自定義變量可以解決同部門下相同工資卻有同樣排序編號問題
SET @rank:=0;
SELECT * FROM
(SELECT a.*,
IF(@tmp=deptno,@rank:=@rank + 1,@rank:=1) AS group_id,
@tmp:=deptno AS tmp
FROM employee a ORDER BY deptno,sal DESC) b
WHERE b.group_id<=5
如圖:
1.3.1 SQL分析
采用了局部變量方法,mysql變量詳情理解:https://jingzh.blog.csdn.net/article/details/96328410
倘若只有一條信息,可能會讓臨時變量一直增加所以先這樣設置SET @rank:=0;
核心語句:
IF(@tmp=deptno,@rank:=@rank + 1,@rank:=1)
則利用中間變量@tmp存儲上一條記錄的deptno,並和當前的對比,如若相同,則序號@rank增加1,否則初始化@rank為1
@tmp:=deptno AS tmp
則用於將當前的province_name
值記錄下來,供下一條記錄使用
1.4 窗口函數
1.4.1 mysql
在mysql8.0
中有相關的內置函數,而且考慮了各種排名問題:
row_number()
: 同記錄不同名,相當於行號,例如3000、2000、2000、1000排名后為1、2、3、4rank()
: 同記錄同名,有跳級
,例如3000、2000、2000、1000排名后為1、2、2、4dense_rank()
: 同薪同名,無跳級
,例如3000、2000、2000、1000排名后為1、2、2、3ntile()
: 分桶排名,即首先按桶的個數分出第一二三桶,然后各桶內從1排名,實際不是很常用
另外
這三個函數必須要要與其搭檔over()
配套使用,over()
中的參數常見的有兩個,分別是
partition by
,按某字段切分order by
,與常規order by
用法一致,也區分ASC
(默認)和DESC
,因為排名總得有個依據
SELECT t.*
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY 分組字段 ORDER BY 排序字段 DESC) rn,
b.*
FROM 表 b) t
WHERE t.rn <= 3 ;
1.4.2 oracle
oracle中是使用over函數
SELECT t.*
FROM (SELECT ROW_NUMBER() OVER(PARTITION BY 分組字段 ORDER BY 排序字段 DESC) rn,
b.*
FROM 表 b) t
WHERE t.rn <= 3 ;
此處使用了析構函數
Oracle中的分析函數over()的詳細解析
兩種寫法都是獲取分組中的偽列序號為目的