神奇的 SQL ,高級處理之 Window Functions → 打破我們的侷限!

開心一刻

  今天兒子跟老婆聊天

  兒子:媽媽,我爲什麼沒有兩個爸爸呀

  老婆:每個人都只有一個爸爸呀,你看誰有兩個爸爸了

  兒子一臉真誠的看着老婆:那你爲什麼就有兩個爸爸呢

  老婆一臉疑惑的望向兒子:我哪有兩個爸爸了?

  兒子有點不服氣,溫柔地說道:你管爺爺叫爸爸,你管姥爺還叫爸爸,這不就是兩個爸爸嗎

  老婆輕聲解釋道:雖然我管他們兩個都叫爸爸,但是姥爺纔是我的爸爸,爺爺是爸爸的爸爸,是我公公,明白了嗎

  兒子兩眼朝天上看了下,若有所思道:公公不是太監嗎

  老婆驚訝道:什麼太監呀,我說的公公和你說的公公不是一回事,你這一天天的腦子裏都想什麼呢

  兒子生氣道:你不用解釋了,待會我就告訴奶奶,你說爺爺是太監

什麼是窗口函數

   Window Function 也稱爲 OLAP(Online Analytical Processing)函數 

  對數據庫數據進行實時分析處理,例如市場分析、財務報表等,是標準的 SQL 功能

  中文翻譯過來,叫 窗口函數 ,或者 開窗函數 ,在 Oracle 中也稱 分析函數 

  與 聚合函數 一樣,也是對集合進行聚合計算,但和 聚合函數 又不一樣,使用 聚合函數 時,每組只返回一個值,但 開窗函數 可以爲組中的每一行返回一個值

  你們懂我說的意思吧

  現在不懂也沒關係哈,繼續往下看,看完之後你肯定就懂了

  支持情況

  既然 窗口函數 是 標準 SQL 功能 ,那關係型數據庫應該都支持吧

   Oracle 11g 、 SQL Server 2008 、 DB2 9.7 、 PostgreSQL 8.4 都支持窗口函數

  但 MySQL 從 8 開始才支持, MySQL5.7 及之前的版本不支持 窗口函數 

  關於對標準 SQL 的支持以及支持程度,還得看各個數據庫廠商,有的支持的早、支持的全,也有的支持的晚、支持的少

  但隨着時間的推移,標準 SQL 終將能在所有的 DBMS 中使用

窗口函數的語法

  基本語法如下

  看着很簡單,但卻很陌生,我們將其進行拆分下

  1、 窗口函數 ,命名一般是見名知意,表明這個函數要實現的功能

  2、 OVER 子句, OVER 是約定好的固定寫法,其內容是規則的指定,告訴 窗口函數 以怎樣的規則去實現功能

     PARTITION BY 類似 GROUP BY ,指定分組規則

     ORDER BY 就跟我們平時使用的 ORDER BY 一樣,指定排序規則

  看完這個語法介紹,我相信大家還是很懵,我非常理解大家

  但先別慌,結合案例來看,慢慢就懂了

  能夠作爲窗口函數使用的函數分兩種

  1、專用窗口函數,如: RANK 、 ROW_NUMBER 、  DENSE_RANK  等等

  2、能夠作爲窗口函數的聚合函數,如: SUM 、 AVG 、 COUNT 、 MAX 、 MIN 

  後續的案例演示我們基於 MySQL8.0.30 ,初始表 tbl_ware 及數據如下

CREATE TABLE `tbl_ware` (
  `ware_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品id',
  `ware_name` VARCHAR(100) NOT NULL COMMENT '商品名稱',
  `ware_category` VARCHAR(100) NOT NULL COMMENT '商品類別',
    `sale_unit_price` INT COMMENT '銷售單價',
    `purchase_unit_price` INT COMMENT '進貨單價',
    `registration_date` DATE COMMENT '等級日期',
  PRIMARY KEY (`ware_id`) USING BTREE
) ENGINE=InnoDB COMMENT='產品';

INSERT INTO tbl_ware VALUES
(1,'T恤衫','衣服',100, 50,'2023-12-15'),
(2,'打孔器','辦公用品',25, 10,'2023-12-15'),
(3,'運動T恤','衣服',150, 50,'2023-12-15'),
(4,'菜刀','廚房用具',75, 30,'2023-12-15'),
(5,'高壓鍋','廚房用具',600, 200,'2023-12-15'),
(6,'叉子','廚房用具',7, 3,'2023-12-15'),
(7,'菜板','廚房用具',98, 30,'2023-12-15'),
(8,'圓珠筆','辦公用品',5, 2,'2023-12-15');
View Code

專用窗口函數

  這些函數是標準 SQL 定義的 OLAP 專用函數,通過函數名很容易看出其 OLAP 的用途

  RANK

  從名字可知,該函數用來排名、排序

  1、假設我們對 tbl_ware 按售價從高到低進行排名, SQL 該如何寫

  相信大家很容易就寫出來了: SELECT * FROM tbl_ware ORDER BY sale_unit_price DESC; 

  用 RANK 也能實現: SELECT *, RANK() OVER(ORDER BY sale_unit_price DESC) AS ranking FROM tbl_ware; 

  2、假設我們對 tbl_ware 按類別進行分組,然後組內按售價從高到低進行排名, SQL 又該如何寫

  有小夥伴一看到分組二字,第一反應肯定想到了 GROUP BY ,不只是你們,我也是一樣的

  但 GROUP BY 往往結合 聚合函數 使用,分組後每組只能得到一個值,顯然滿足不了需求

  但 RANK 可以: SELECT *, RANK() OVER(PARTITION BY ware_category ORDER BY sale_unit_price DESC) AS ranking FROM tbl_ware; 

   PARTITION BY 對錶的橫向進行分組,類似 GROUP BY ,但不具備聚合功能

   ORDER BY 則決定了縱向排序的規則,與 SELECT 子句末尾的 ORDER BY 子句完全相同

  通過 PARTITION BY 分組後的記錄集合稱爲“窗口”,代表“範圍”。這也是 窗口函數 名稱的由來

  DENSE_RANK

  一看名字就知道跟 RANK 有關係,爲了對比它倆的區別,需要補充幾條數據

INSERT INTO tbl_ware VALUES
(9,'帶帽衛衣','衣服', 150, 90, '2023-12-15'),
(10,'砍骨刀','廚房用具', 150, 69, '2023-12-15');
View Code

   RANK 排序時,如果存在相同位次的記錄,會跳過之後的位次,如: 1,2,2,2,5 , 3,4 被跳過了

   DENSE_RANK 排序時,如果存在相同位次的記錄,則不會跳過之後的位次,如: 1,2,2,2,3,4 

  ROW_NUMBER

  獲取行數或者行號

  如果我們想按售價從高到低排序後,獲取每一行的行號, SQL 可寫成: SELECT *, ROW_NUMBER() OVER(ORDER BY sale_unit_price DESC) AS row_num FROM tbl_ware; 

  如果再加上一個分組: SELECT *, ROW_NUMBER() OVER(PARTITION BY ware_category ORDER BY sale_unit_price DESC) AS row_num FROM tbl_ware; 

  此刻大家應該想起點什麼了

  分組取前N條,是不是很適合用這種方式實現?

  我都跟你們實現好了:MySQL 分組排序後 → 如何取前N條或倒數N條

  還有其他的 專用窗口函數 就不一一做介紹了,大家可以去各個數據庫的官網進行查閱

聚合函數的窗口化使用

  所有的 聚合函數 都能用作窗口函數,其語法和 專用窗口函數 完全相同

  作爲窗口化使用後, 聚合函數 實現的效果就發生了很大的變化,我們來看具體案例

  SUM

  作爲 聚合函數 , SUM 的作用想必大家都很清楚了

  但是窗口化之後了,我們來看看效果

  發現什麼了?

  並不是一個單獨的彙總值,而是逐行彙總,是不是有點意思?

  如果再加上分組

  分組後,對每一組進行逐行彙總

  AVG

  類比 SUM ,我們直接看分組的情況

  分組後,對每一組的每一行求歷史平均值

  其他 聚合函數 的窗口化就不一一演示了,相信大家也都明白了

窗口函數的適用範圍

  通過上述的幾個案例,相信大家對這個問題已經有了一個大致的答案

   窗口函數 只能在 SELECT 子句中使用,不能在 WHERE 子句或者 GROUP BY 子句中使用,爲什麼了?

  因爲 窗口函數 是對 WHERE 子句或者 GROUP BY 子句處理後的“結果”進行的逐行操作

  我們換個角度來看, 窗口函數 是不會改變結果行數的,而 WHERE 是會改變結果行數的,那把 窗口函數 放到 WHERE 子句的意義何在?

  所以一不做二不休,直接在語法上做了這樣的限制: 窗口函數 只能在 SELECT 子句中使用

總結

  1、 窗口函數 是標準的 SQL 功能,而非特定數據庫的功能

     SQL 功能的落地還得依賴各個數據庫廠商

    提供了標準,數據庫廠商不一定實現,或者說暫時不實現

  2、 窗口函數 與 聚合函數 並非矛盾,二者是互補關係

  3、之所以對 窗口函數 這麼陌生,主要是我們使用太少,但是其在報表分析方面還是很有作用的

  4、 窗口函數 的使用範圍很有限,你可以隨意使用,報語法錯誤了再調整唄

參考

  《SQL 基礎教程》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章