
窗口函数是 MySQL 8.0 引入的一个重要特性,它可以在不改变数据行数的前提下,对每一行数据进行聚合、排名、累积等计算。与 GROUP BY 不同,窗口函数会保留所有原始行。
一、窗口函数
窗口函数的执行逻辑是:在每一行数据上,基于“窗口”(一个定义好的数据范围)进行计算,然后把计算结果的附加列(如排名、累计值等)直接写到对应的行上,不合并行。
语法结构
SELECT
列名,
窗口函数() OVER (
PARTITION BY 分区列 -- 分组
ORDER BY 排序列 -- 排序
ROWS/RANGE BETWEEN ... -- 窗口范围
) AS 别名
FROM 表名;
二、窗口函数的三大类别
1. 排名函数
| 函数 | 说明 |
|---|---|
ROW_NUMBER() | 按顺序编号,不处理并列(1,2,3,4...) |
RANK() | 跳跃排名,并列后跳过(1,1,3,4...) |
DENSE_RANK() | 连续排名,并列后不跳过(1,1,2,3...) |
NTILE(n) | 将数据分成 n 组,返回组号 |
示例:按成绩排名
-- 创建示例表
CREATE TABLE students (
id INT,
name VARCHAR(50),
score INT,
class VARCHAR(10)
);
INSERT INTO students VALUES
(1, 'Alice', 95, 'A'),
(2, 'Bob', 85, 'A'),
(3, 'Charlie', 95, 'A'),
(4, 'David', 78, 'B'),
(5, 'Eva', 92, 'B'),
(6, 'Frank', 85, 'B');
-- 各排名函数的对比
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rank,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank,
NTILE(3) OVER (ORDER BY score DESC) AS ntile
FROM students;
结果:
+---------+-------+---------+------+------------+-------+
| name | score | row_num | rank | dense_rank | ntile |
+---------+-------+---------+------+------------+-------+
| Alice | 95 | 1 | 1 | 1 | 1 |
| Charlie | 95 | 2 | 1 | 1 | 1 |
| Eva | 92 | 3 | 3 | 2 | 2 |
| Bob | 85 | 4 | 4 | 3 | 2 |
| Frank | 85 | 5 | 4 | 3 | 3 |
| David | 78 | 6 | 6 | 4 | 3 |
+---------+-------+---------+------+------------+-------+
区别:
-
ROW_NUMBER():1,2,3,4,5,6 -
RANK():1,1,3,4,4,6 -
DENSE_RANK():1,1,2,3,3,4 -
NTILE(3):1,1,2,2,3,3
2. 聚合函数(作为窗口函数)
| 函数 | 说明 |
|---|---|
SUM() | 窗口内的累计和 |
AVG() | 窗口内的平均值 |
COUNT() | 窗口内的行数 |
MAX() / MIN() | 窗口内的最大值/最小值 |
示例:累计求和与移动平均
SELECT
date,
sales,
SUM(sales) OVER (ORDER BY date) AS cumulative_sales,
AVG(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3
FROM sales_data;
3. 取值函数
| 函数 | 说明 |
|---|---|
LAG() | 获取当前行之前的第 N 行 |
LEAD() | 获取当前行之后的第 N 行 |
FIRST_VALUE() | 窗口内的第一行 |
LAST_VALUE() | 窗口内的最后一行 |
示例:计算环比增长率
SELECT
month,
revenue,
LAG(revenue, 1) OVER (ORDER BY month) AS prev_month_revenue,
ROUND(
(revenue - LAG(revenue, 1) OVER (ORDER BY month)) /
LAG(revenue, 1) OVER (ORDER BY month) * 100, 2
) AS growth_rate_percent
FROM monthly_revenue;
三、PARTITION BY(分组计算)
PARTITION BY 将数据分组,窗口函数在每个分组内独立计算。
SELECT
class,
name,
score,
RANK() OVER (PARTITION BY class ORDER BY score DESC) AS rank_in_class
FROM students;
结果:
+-------+---------+-------+---------------+
| class | name | score | rank_in_class |
+-------+---------+-------+---------------+
| A | Alice | 95 | 1 |
| A | Charlie | 95 | 1 |
| A | Bob | 85 | 3 |
| B | Eva | 92 | 1 |
| B | Frank | 85 | 2 |
| B | David | 78 | 3 |
+-------+---------+-------+---------------+
四、窗口范围(Frame)
窗口范围决定计算时包含哪些行:
| 写法 | 说明 |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | 从开始到当前行(默认累计) |
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW | 当前行及前 2 行 |
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING | 前 1 行 + 当前行 + 后 1 行 |
ROWS UNBOUNDED PRECEDING | 从开始到当前行的简写 |
RANGE BETWEEN ... | 基于值的范围(而不是行数) |
SELECT
date,
sales,
SUM(sales) OVER (ORDER BY date) AS cumulative, -- 默认从开始到当前
SUM(sales) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS last_3_days,
SUM(sales) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total_all
FROM sales_data;
五、窗口函数 vs GROUP BY
| 对比维度 | GROUP BY | 窗口函数 OVER() |
|---|---|---|
| 行数变化 | 合并行,行数减少 | 保持原行数 |
| 结果位置 | 单独的结果行 | 附加在原行旁边 |
| 子查询需求 | 常需要子查询 | 不需要 |
| 适用场景 | 汇总统计 | 排名、累计、移动平均 |
-- GROUP BY:只能看到汇总结果
SELECT department, AVG(salary) FROM employees GROUP BY department;
-- 窗口函数:每行都保留,同时显示平均工资
SELECT
employee_id,
name,
department,
salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg_salary
FROM employees;

2400

被折叠的 条评论
为什么被折叠?



