oracle或mysql獲取分組后每組的前三條數據


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、4
  • rank(): 同記錄同名,有跳級,例如3000、2000、2000、1000排名后為1、2、2、4
  • dense_rank(): 同薪同名,無跳級,例如3000、2000、2000、1000排名后為1、2、2、3
  • ntile(): 分桶排名,即首先按桶的個數分出第一二三桶,然后各桶內從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()的詳細解析
兩種寫法都是獲取分組中的偽列序號為目的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM