MySQL 中的窗口函数

窗口函数是 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;
相关推荐
严同学正在努力1 小时前
Oracle数据技术运维大全(下篇)
运维·数据库·ai·oracle·架构
catino1 小时前
spring-事务@Transactional
java·数据库·spring
数智启示录1 小时前
Apache Doris 4.0.8 数据建模实战(第 2 篇):同一批订单写进三种模型,为什么得到三个答案
大数据·数据库·面试
数智启示录1 小时前
Apache Doris 4.0.8 CDC 正确性(第 9 篇):Flink Checkpoint 一直成功,表里为什么仍是旧数据
大数据·数据库·经验分享·面试·flink
拾光Ծ3 小时前
【MySQL】对表数据的操作:增删查改(CRUD)
android·数据库·sql·mysql
这个DBA有点耶16 小时前
2026年分布式数据库有哪些主流选择?先评估这5个维度再决定
数据库·分布式·dba
这个DBA有点耶19 小时前
从MySQL 5.7到8.0:JSON查询性能差在哪?虚拟列索引vs多值索引怎么选
数据库·mysql·架构
胡写代码19 小时前
数据库审计字段,别再每张表各写一套了——我统一成这 6 个字段
数据库