MySQL窗口函數(shù) OVER()全解析
一、窗口函數(shù)概述
1. 什么是窗口函數(shù)?
窗口函數(shù)(Window Function)是對(duì)一組行(稱為"窗口")執(zhí)行計(jì)算,并為每一行返回一個(gè)值的函數(shù)。與聚合函數(shù)不同,窗口函數(shù)不減少行數(shù)。
2. 窗口函數(shù) vs 聚合函數(shù)
| 特性 | 窗口函數(shù) | 聚合函數(shù) |
|---|---|---|
| 返回行數(shù) | 與輸入行數(shù)相同 | 通常減少行數(shù)(GROUP BY) |
| 分組效果 | 保留所有行,添加計(jì)算結(jié)果 | 每組返回一行 |
| 語(yǔ)法位置 | SELECT 子句中 | SELECT 或 HAVING 子句中 |
| 典型函數(shù) | ROW_NUMBER(), RANK(), SUM() OVER() | SUM(), COUNT(), AVG() |
3. 基本語(yǔ)法結(jié)構(gòu)
窗口函數(shù)([參數(shù)]) OVER ( [PARTITION BY <分組列>] [ORDER BY <排序列 ASC/DESC>] [ROWS BETWEEN 開(kāi)始行 AND 結(jié)束行] )
- OVER() 里面不能直接放
GROUP BY!可以放PARTITION BY PARTITION BY子句用于指定分組列,關(guān)鍵字:PARTITION BY。ORDER BY子句用于指定排序列,關(guān)鍵字ORDER BY。ROWS BETWEEN子句用于指定窗口的范圍,關(guān)鍵字ROWS BETWEEN即[開(kāi)始行]、[結(jié)束行]
其中,ROWS BETWEEN 子句在實(shí)際中可能用得相對(duì)少一些,因此有部分參考資料的語(yǔ)法描述省略了ROWS BETWEEN 子句,主要側(cè)重于PARTITION BY分組與ORDER BY排序:
二、窗口函數(shù)核心組成部分
1. PARTITION BY - 分區(qū)子句
將數(shù)據(jù)劃分為多個(gè)分區(qū),在每個(gè)分區(qū)內(nèi)獨(dú)立計(jì)算。
-- 創(chuàng)建測(cè)試數(shù)據(jù)
CREATE TABLE sales (
id INT PRIMARY KEY AUTO_INCREMENT,
salesperson VARCHAR(50),
region VARCHAR(50),
sale_date DATE,
amount DECIMAL(10, 2)
);
INSERT INTO sales (salesperson, region, sale_date, amount) VALUES
('張三', '北京', '2024-01-01', 1000.00),
('張三', '北京', '2024-01-02', 1500.00),
('李四', '上海', '2024-01-01', 2000.00),
('李四', '上海', '2024-01-02', 2500.00),
('王五', '北京', '2024-01-01', 1200.00),
('王五', '北京', '2024-01-03', 1800.00),
('趙六', '廣州', '2024-01-02', 2200.00);
-- 按銷(xiāo)售員分區(qū)計(jì)算
SELECT
salesperson,
sale_date,
amount,
-- 每個(gè)銷(xiāo)售員的銷(xiāo)售總額
SUM(amount) OVER (PARTITION BY salesperson) AS total_by_person,
-- 每個(gè)地區(qū)的銷(xiāo)售總額
SUM(amount) OVER (PARTITION BY region) AS total_by_region,
-- 不分區(qū)(全局總額)
SUM(amount) OVER () AS grand_total
FROM sales
ORDER BY salesperson, sale_date;輸出結(jié)果:
salesperson | sale_date | amount | total_by_person | total_by_region | grand_total
------------|------------|--------|-----------------|-----------------|------------
張三 | 2024-01-01 | 1000.00| 2500.00 | 5500.00 | 12200.00
張三 | 2024-01-02 | 1500.00| 2500.00 | 5500.00 | 12200.00
李四 | 2024-01-01 | 2000.00| 4500.00 | 4500.00 | 12200.00
李四 | 2024-01-02 | 2500.00| 4500.00 | 4500.00 | 12200.00
王五 | 2024-01-01 | 1200.00| 3000.00 | 5500.00 | 12200.00
王五 | 2024-01-03 | 1800.00| 3000.00 | 5500.00 | 12200.00
趙六 | 2024-01-02 | 2200.00| 2200.00 | 2200.00 | 12200.00
2. ORDER BY - 排序子句
在分區(qū)內(nèi)對(duì)行進(jìn)行排序,影響排名函數(shù)和累計(jì)計(jì)算。
SELECT
salesperson,
sale_date,
amount,
-- 按金額排序(分區(qū)內(nèi))
ROW_NUMBER() OVER (PARTITION BY salesperson ORDER BY amount DESC) AS rn,
-- 累計(jì)金額(分區(qū)內(nèi)按日期排序)
SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) AS running_total,
-- 移動(dòng)平均值(最近3行的平均值)
AVG(amount) OVER (PARTITION BY salesperson ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3
FROM sales
ORDER BY salesperson, sale_date;3.聚合窗口函數(shù)
許多窗口函數(shù)的教程,通常將常用的窗口函數(shù)分為兩大類:聚合窗口函數(shù) 與 專用窗口函數(shù)。聚合窗口函數(shù)的函數(shù)名與普通常用聚合函數(shù)一致,功能也一致。從使用的角度來(lái)講,與普通聚合函數(shù)的區(qū)別在于提供了窗口函數(shù)的專屬子句,來(lái)使得數(shù)據(jù)的分析與獲取更簡(jiǎn)便。主要有如下幾個(gè):
| 函數(shù)名 | 作用 |
|---|---|
| SUM | 對(duì)指定列的數(shù)值求和 |
| AVG | 計(jì)算指定列的平均值 |
| COUNT | 統(tǒng)計(jì)記錄/非空值數(shù)量 |
| MAX | 找出指定列的最大值 |
| MIN | 找出指定列的最小值 |
4.專用窗口函數(shù)
常見(jiàn)的專用窗口函數(shù)
:
| 函數(shù)名 | 分類 | 說(shuō)明 |
|---|---|---|
| RANK | 排序函數(shù) | 類似于排名,并列的結(jié)果序號(hào)可以重復(fù),序號(hào)不連續(xù)(如:1,2,2,4) |
| DENSE_RANK | 排序函數(shù) | 類似于排名,并列的結(jié)果序號(hào)可以重復(fù),序號(hào)連續(xù)(如:1,2,2,3) |
| ROW_NUMBER | 排序函數(shù) | 對(duì)分組下的所有結(jié)果排序,基于分組分配唯一連續(xù)的行號(hào)(如:1,2,3,4) |
| PERCENT_RANK | 分布函數(shù) | 每行按公式 (rank-1) / (rows-1) 計(jì)算,結(jié)果為0~1的百分比值 |
| CUME_DIST | 分布函數(shù) | 分組內(nèi)小于等于當(dāng)前rank值的行數(shù) ÷ 分組內(nèi)總行數(shù),結(jié)果為0~1的百分比值 |
5. ROWS BETWEEN - 窗口幀子句
定義窗口函數(shù)的計(jì)算范圍。
-- 各種窗口幀的示例
SELECT
salesperson,
sale_date,
amount,
-- 默認(rèn):分區(qū)內(nèi)所有行
SUM(amount) OVER (PARTITION BY salesperson) AS total_all,
-- ROWS模式:物理行
SUM(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_rows,
-- RANGE模式:邏輯值范圍(相同值的行視為同一幀)
SUM(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_range,
-- 滑動(dòng)窗口:當(dāng)前行及前2行
SUM(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS sum_last_3,
-- 前后各一行
SUM(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS sum_neighbors
FROM sales
ORDER BY salesperson, sale_date;三、窗口函數(shù)分類詳解
1. 序號(hào)函數(shù)(Ranking Functions)
-- 創(chuàng)建測(cè)試數(shù)據(jù)
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
department VARCHAR(50),
salary DECIMAL(10, 2)
);
INSERT INTO employees (name, department, salary) VALUES
('張三', '技術(shù)部', 8000.00),
('李四', '技術(shù)部', 9000.00),
('王五', '技術(shù)部', 9500.00),
('趙六', '技術(shù)部', 9000.00),
('錢(qián)七', '銷(xiāo)售部', 7000.00),
('孫八', '銷(xiāo)售部', 8500.00),
('周九', '銷(xiāo)售部', 8500.00),
('吳十', '銷(xiāo)售部', 7500.00);
-- 1. ROW_NUMBER():連續(xù)不重復(fù)的序號(hào)
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num
FROM employees;
-- 2. RANK():有間隔的排名(相同值排名相同,下一個(gè)排名跳躍)
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_num
FROM employees;
-- 3. DENSE_RANK():無(wú)間隔的排名(相同值排名相同,下一個(gè)排名連續(xù))
SELECT
name,
department,
salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rank_num
FROM employees;
-- 4. NTILE(n):將數(shù)據(jù)分為n組
SELECT
name,
department,
salary,
NTILE(4) OVER (PARTITION BY department ORDER BY salary DESC) AS quartile
FROM employees;輸出對(duì)比:
部門(mén) | 姓名 | 薪資 | ROW_NUMBER | RANK | DENSE_RANK | NTILE(4)
------|------|--------|------------|------|------------|---------
技術(shù)部 | 王五 | 9500 | 1 | 1 | 1 | 1
技術(shù)部 | 李四 | 9000 | 2 | 2 | 2 | 1
技術(shù)部 | 趙六 | 9000 | 3 | 2 | 2 | 2
技術(shù)部 | 張三 | 8000 | 4 | 4 | 3 | 2
銷(xiāo)售部 | 孫八 | 8500 | 1 | 1 | 1 | 1
銷(xiāo)售部 | 周九 | 8500 | 2 | 1 | 1 | 1
銷(xiāo)售部 | 吳十 | 7500 | 3 | 3 | 2 | 2
銷(xiāo)售部 | 錢(qián)七 | 7000 | 4 | 4 | 3 | 2
2. 分布函數(shù)(Distribution Functions)
-- 5. PERCENT_RANK():百分比排名 (rank - 1) / (total_rows - 1)
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary) AS rank_num,
PERCENT_RANK() OVER (PARTITION BY department ORDER BY salary) AS percent_rank
FROM employees;
-- 6. CUME_DIST():累計(jì)分布(小于等于當(dāng)前值的行數(shù) / 總行數(shù))
SELECT
name,
department,
salary,
CUME_DIST() OVER (PARTITION BY department ORDER BY salary) AS cume_dist
FROM employees;
-- 7. PERCENTILE_CONT():連續(xù)百分位數(shù)(需要MySQL 8.0.2+)
SELECT
department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY department) AS median_salary
FROM employees
GROUP BY department, salary;
-- 8. PERCENTILE_DISC():離散百分位數(shù)
SELECT
department,
PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY department) AS median_salary
FROM employees
GROUP BY department, salary;3. 前后值函數(shù)(Value Functions)
-- 9. LAG(column, n, default):獲取前n行的值
SELECT
name,
department,
salary,
LAG(salary, 1, 0) OVER (PARTITION BY department ORDER BY salary) AS prev_salary,
salary - LAG(salary, 1, 0) OVER (PARTITION BY department ORDER BY salary) AS salary_diff
FROM employees;
-- 10. LEAD(column, n, default):獲取后n行的值
SELECT
name,
department,
sale_date,
amount,
LEAD(amount, 1, 0) OVER (PARTITION BY salesperson ORDER BY sale_date) AS next_amount,
LEAD(sale_date, 1, NULL) OVER (PARTITION BY salesperson ORDER BY sale_date) AS next_date
FROM sales;
-- 11. FIRST_VALUE(column):窗口內(nèi)第一個(gè)值
SELECT
name,
department,
salary,
FIRST_VALUE(salary) OVER (
PARTITION BY department
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_salary,
salary - FIRST_VALUE(salary) OVER (
PARTITION BY department
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS diff_from_lowest
FROM employees;
-- 12. LAST_VALUE(column):窗口內(nèi)最后一個(gè)值(注意默認(rèn)窗口幀?。?
SELECT
name,
department,
salary,
-- 錯(cuò)誤用法:默認(rèn)窗口幀是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary) AS wrong_last_value,
-- 正確用法:指定完整的窗口幀
LAST_VALUE(salary) OVER (
PARTITION BY department
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS correct_last_value,
-- 或者使用NTH_VALUE
NTH_VALUE(salary, 1) OVER (
PARTITION BY department
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS first_salary,
NTH_VALUE(salary, 2) OVER (
PARTITION BY department
ORDER BY salary
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS second_salary
FROM employees;4. 聚合函數(shù)作為窗口函數(shù)
-- 所有聚合函數(shù)都可以作為窗口函數(shù)使用
SELECT
salesperson,
region,
sale_date,
amount,
-- 聚合函數(shù)
COUNT(*) OVER (PARTITION BY salesperson) AS total_transactions,
SUM(amount) OVER (PARTITION BY salesperson) AS total_amount,
AVG(amount) OVER (PARTITION BY salesperson) AS avg_amount,
MAX(amount) OVER (PARTITION BY salesperson) AS max_amount,
MIN(amount) OVER (PARTITION BY salesperson) AS min_amount,
-- 標(biāo)準(zhǔn)差和方差(MySQL 8.0+)
STDDEV(amount) OVER (PARTITION BY salesperson) AS std_amount,
VARIANCE(amount) OVER (PARTITION BY salesperson) AS var_amount,
-- 累計(jì)聚合
SUM(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total,
-- 移動(dòng)平均
AVG(amount) OVER (
PARTITION BY salesperson
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3,
-- 百分比
amount * 100.0 / SUM(amount) OVER (PARTITION BY salesperson) AS percentage
FROM sales
ORDER BY salesperson, sale_date;到此這篇關(guān)于MySQL窗口函數(shù) OVER()講解的文章就介紹到這了,更多相關(guān)mysql 窗口函數(shù)over()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
You must SET PASSWORD before execut
今天在MySql5.6操作時(shí)報(bào)錯(cuò):You must SET PASSWORD before executing this statement解決方法,需要的朋友可以參考下2013-06-06
使用mysqldump實(shí)現(xiàn)mysql備份
mysqldump客戶端可用來(lái)轉(zhuǎn)儲(chǔ)數(shù)據(jù)庫(kù)或搜集數(shù)據(jù)庫(kù)進(jìn)行備份或?qū)?shù)據(jù)轉(zhuǎn)移到另一個(gè)SQL服務(wù)器(不一定是一個(gè)MySQL服務(wù)器)。今天我們就來(lái)詳細(xì)探討下mysqldump的使用方法2016-11-11
MySQL數(shù)據(jù)庫(kù)優(yōu)化技術(shù)之配置技巧總結(jié)
這篇文章主要介紹了MySQL數(shù)據(jù)庫(kù)優(yōu)化技術(shù)之配置技巧,較為詳細(xì)的總結(jié)分析了MySQL進(jìn)行硬件級(jí)軟件優(yōu)化的相關(guān)方法與注意事項(xiàng),需要的朋友可以參考下2016-07-07
解決MySQL Sending data導(dǎo)致查詢很慢問(wèn)題的方法與思路
這篇文章主要介紹了解決MySQL Sending data導(dǎo)致查詢很慢問(wèn)題的方法與思路,感興趣的小伙伴們可以參考一下2016-04-04
詳解MySQL的數(shù)據(jù)行和行溢出機(jī)制
在前面的文章中,白日夢(mèng)曾不止一次的提及到:InnoDB從磁盤(pán)中讀取數(shù)據(jù)的最小單位是數(shù)據(jù)頁(yè)。 而你想得到的id = xxx的數(shù)據(jù),就是這個(gè)數(shù)據(jù)頁(yè)眾多行中的一行。 這篇文章我們就一起來(lái)看一下數(shù)據(jù)行設(shè)計(jì)的多么巧妙。2020-11-11
MySQL?5.7中NULL與‘?‘空字符值的多維度分析(詳解)
在數(shù)據(jù)庫(kù)設(shè)計(jì)和開(kāi)發(fā)過(guò)程中,正確理解和使用NULL值對(duì)于確保數(shù)據(jù)質(zhì)量和查詢效率至關(guān)重要,本文將從多個(gè)維度對(duì)NULL值進(jìn)行深入分析,并與空字符串''以及其他控制進(jìn)行對(duì)比,旨在為讀者提供一個(gè)全面而清晰的理解,感興趣的朋友跟隨小編一起看看吧2024-12-12
InnoDB中不同SQL語(yǔ)句設(shè)置鎖的情況詳解
這篇文章主要介紹了InnoDB中不同SQL語(yǔ)句設(shè)置鎖的情況詳解,在Mysql中,鎖定讀、更新、刪除操作通常會(huì)對(duì)SQL語(yǔ)句處理過(guò)程中掃描到的每條索引記錄設(shè)置記錄鎖,需要的朋友可以參考下2024-01-01

