技術干貨實戰(6)- MySQL分組查詢后如何獲取每組的前N條數據,你會嗎?

作者: 修羅debug
版權聲明:本文為博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。


“分組查詢”可以說是相當常見的SQL查詢語句,對于MySQL數據庫而言,其實現分組查詢的關鍵字為GROUP BY,而在使用GROUP BY期間一般還會有其他的聚合函數配合使用,比如計數用的COUNT(*),統計數值和用的SUM(*),而本文要介紹的是另一種類型的“分組查詢”,即分組查詢出來后再查詢出每一組的前N條數據。


為了方便諸位理解,還是直接舉一個實際的案例吧:存在兩個數據庫表,一個叫課程表course,另一個叫課程類型表course_type,這兩個數據庫表的DDL(數據庫表字段定義)如下所示:

1)課程類型表:   

CREATE TABLE `course_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '類型名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='課程類型';


2)課程信息表:

CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type_id` int(11) NOT NULL COMMENT '類型id',
`name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '課程名稱',
`scan_total` int(255) DEFAULT NULL COMMENT '課程瀏覽量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='課程信息表';


現在的功能需求是:找出每種課程類型中課程瀏覽量排名前3的課程記錄,而這種場景經??梢栽谝恍┰诰€教育平臺中見到,如下圖所示為某個在線教育平臺中“后端開發”這一大類型里面每種小分類的課程銷量排行榜:



接下來,我們將找尋各種方式去實現這一功能需求!

1傳統的實現方式一般是采用Java代碼的方式先查詢出每種課程類型數據,然后再遍歷每一種課程類型,在課程表中根據課程類型匹配查詢出瀏覽量前3的課程數據,即 type_id=xx order by scan_total desc limit 3;


其代碼實現方式在這里就不貼出來了,感興趣的小伙伴可以自己動手擼一擼!

此種實現方式最終固然可以實現功能需求,但是,有一個不好的地方在于需要在遍歷每一種課程類型時不斷發出查詢課程數據列表的SQL,如果課程類型有10幾種,而每一種需要取幾十、甚至幾百條數據,那將耗費很大的資源(建立數據庫鏈接是需要耗資源的:內存、CPU、網絡、磁盤


2因此,我們轉而求其次,采用SQL查詢一次性來搞定!在進行實操之前,debug建議諸位先打開Navicat嘗試擼一擼,然后再回過頭來看看debug提供的實現方式.


為了實現這種功能,我們需要轉換下思考的角度:仔細閱讀需求,可以得知它是需要我們查找出每種課程類型下課程瀏覽量前3的課程列表,其實就是找出每個課程在同種課程類型下的瀏覽量排名,最后再找出每種課程類型下排名前3的課程!


如下圖所示為課程信息表,其中,最后一列為debug自己計算出的每個課程在同種課程類型下課程列表中的排名,即top值:


 

朝著上圖這個方向努力了,我們擼出了相應的SQL,如下所示:

SELECT
a.id,
a.type_id,
c.`name` AS typeName,
a.scan_total,
a.`name`,
(
SELECT
COUNT(b.id)
FROM
course AS b
WHERE
b.type_id = a.type_id
AND b.scan_total > a.scan_total
) AS top
FROM
course AS a
LEFT JOIN course_type AS c ON c.id = a.type_id
ORDER BY
a.type_id ASC,
a.scan_total DESC;


執行上述SQL后得到的結果如下圖所示:



3)到這里我們已經將每種類型下每個課程的排名top計算出來了,需要注意的是,在上圖得到的結果中,因為Count(b.id) 得到的值可能為 0 ,因此0代表的就是第 1 名;

可能有些小伙伴還有些疑惑,為什么加個子查詢就可以得到上圖中的結果呢?其執行過程是怎么樣的呢?OK,一圖以蔽之,直接看下圖相信就可以解答你心中的疑惑了:


 

4)最后是直接在外層嵌一個大的查詢,然后取排名值 top < 3 的數據列表,即可以得到實現功能需求,其完整的SQL如下所示:   

SELECT
t.*
FROM
(
SELECT
a.id,
a.type_id,
c.`name` AS typeName,
a.scan_total,
a.`name`,
(
SELECT
COUNT(b.id)
FROM
course AS b
WHERE
b.type_id = a.type_id
AND b.scan_total > a.scan_total
) AS top
FROM
course AS a
LEFT JOIN course_type AS c ON c.id = a.type_id
ORDER BY
a.type_id ASC,
a.scan_total DESC
) AS t
WHERE
t.top < 3


執行上述SQL后即可以得到相應的結果,如下圖所示:


至此,我們已經完成了本文開頭提出來的功能需求;那……還有沒有其他的實現方式呢?當然有,只不過其實現起來雖然不同,但是其本質思想跟本文開頭debug提到的那樣“計算出排名top值”是差不多的;


諾,這就是另外的實現方式,從SQL語句就可以看出來,它是上述第一種實現方式的變形:   

SELECT
t.*
FROM
course AS t
WHERE
(
SELECT
COUNT(*)
FROM
course AS c
WHERE
c.type_id = t.type_id
AND c.scan_total > t.scan_total
) < 3
ORDER BY
t.type_id ASC,
t.scan_total DESC


OK,本文講解到此介紹,打完收工,咱們下期再見!


總結

1)代碼/數據庫下載:本文涉及到的數據庫建表語句DDL以及對應的測試數據案例 可以通過關注微信公眾號:程序員實戰基地(掃描下圖微信公眾號即可),回復數字: 1001 ,即可下載 !