“分組查詢”可以說是相當常見的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 desclimit 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 topFROM course AS aLEFT JOIN course_type AS c ON c.id = a.type_idORDER 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 tWHERE t.top < 3
執行上述SQL後即可以得到相應的結果,如下圖所示:
至此,我們已經完成了本文開頭提出來的功能需求;那……還有沒有其他的實現方式呢?當然有,只不過其實現起來雖然不同,但是其本質思想跟本文開頭debug提到的那樣“計算出排名top值”是差不多的;
諾,這就是另外的實現方式,從SQL語句就可以看出來,它是上述第一種實現方式的變形:
SELECT t.*FROM course AS tWHERE ( SELECT COUNT(*) FROM course AS c WHERE c.type_id = t.type_id AND c.scan_total > t.scan_total ) < 3ORDER BY t.type_id ASC, t.scan_total DESC
OK,本文講解到此介紹,打完收工,咱們下期再見!
總結
我是debug,一個相信技術改變生活、技術成就夢想 的攻城獅;如果本文對你有幫助,請三連喲~