MySQL 8.0 新特性 | 窗口函数入门,排名实战
前言在前序内容中我们掌握了事务、隔离级别等数据库核心机制从本篇开始正式学习MySQL 8.0 重磅新特性 —— 窗口函数。窗口函数是数据分析、报表统计、岗位排名、分组 TopN 场景的利器也是中高级开发、数据分析师面试高频必考知识点。在 MySQL 5.7 及更早版本中实现分组排名、累计统计、行间对比等复杂需求需要嵌套大量子查询、关联多张表代码冗长且性能低下。MySQL 8.0 正式引入窗口函数仅用简短语法就能实现复杂分析逻辑不改变原始数据行数兼顾可读性与执行效率。本篇聚焦三大核心排名类窗口函数从概念、语法、分区排序、函数差异、全场景实战、避坑要点逐层讲解案例贴合职场真实业务一、本章知识点汇总窗口函数的定义、作用与核心优势对比传统聚合函数差异窗口函数标准语法OVER()、PARTITION BY、ORDER BY详解三大排名类窗口函数ROW_NUMBER()、RANK()、DENSE_RANK()核心区别全局排名、分组排名两种主流实战场景分组取 TopN 经典面试题型窗口函数标准解法窗口函数执行顺序与使用限制窗口函数与 GROUP BY 聚合函数的本质区别职场高频踩坑点与性能优化建议综合实战案例拓展课后练习题 完整参考答案二、各知识点详解1. 什么是窗口函数窗口函数Window Function也叫开窗函数是 MySQL 8.0 及以上版本支持的特殊函数。它会将查询结果集划分为若干个 “窗口”在每个窗口内完成计算、排名、统计等操作核心特点不会合并数据行原始数据全部保留。核心优势传统GROUP BY会将多行数据聚合为一行丢失明细窗口函数保留所有明细行同时附加统计 / 排名结果。简化复杂逻辑替代多层子查询、多表关联代码更简洁。性能更优MySQL 对窗口函数做了底层优化大数据量场景执行效率高于传统写法。典型适用场景学生成绩排名、员工薪资排名、商品销量排名分组内排名、分组取前 N 名TopN累计求和、移动平均值、行间数据对比下篇讲解日报 / 周报、数据分析报表制作2. 窗口函数标准语法所有窗口函数都遵循函数名() OVER()固定结构OVER()子句用于定义窗口范围包含三大可选子句。sql-- 完整通用语法 窗口函数名() OVER ( [PARTITION BY 字段1, 字段2 ...] -- 分区分组可选 [ORDER BY 排序字段 ASC|DESC] -- 窗口内排序可选 [窗口帧范围] -- 高级用法本篇暂不深入 ) AS 别名;关键字逐句解析OVER()窗口函数的标志括号内为空时代表整个结果集为一个窗口。PARTITION BY分区等价于GROUP BY将数据划分为多个独立窗口分区之间相互隔离排名、计算互不干扰。省略则代表全局一个窗口。ORDER BY对每个窗口内的数据进行排序排名类窗口函数必须搭配 ORDER BY否则排名无意义。窗口帧范围用于定义窗口内的计算行范围排名场景默认即可进阶统计场景使用。3. 三大核心排名窗口函数本篇重点面试必背这三个函数专门用于排名是入门和面试的核心三者最大差异体现在并列名次的处理规则上。表格函数名称核心功能并列数据规则排名序列示例适用场景ROW_NUMBER()为每行分配唯一连续序号分数相同也会区分序号无并列1,2,3,4,5纯行号、分页、强制区分顺序RANK()标准排名同分同名次后续名次跳跃1,2,2,4,5体育赛事排名、通用榜单DENSE_RANK()密集排名同分同名次后续名次连续1,2,2,3,4成绩档位、等级划分、榜单密集排名核心区分口诀ROW_NUMBER人人有编号绝不并列RANK并列会跳号DENSE_RANK并列不跳号。4. 窗口函数与 GROUP BY 聚合函数的核心区别表格对比项窗口函数GROUP BY 聚合函数数据行数保留原始所有行不合并分组后多行合并为一行丢失明细计算范围分区内独立计算明细 统计共存整组聚合计算仅展示聚合结果排序能力支持分区内单独排序仅支持全局排序组内排序繁琐执行顺序晚于 WHERE、GROUP BY早于 LIMIT晚于 WHERE早于 HAVING典型用法排名、行间对比、累计计算求和、计数、平均值等全局汇总5. 基础使用限制避坑前置知识窗口函数不能直接写在 WHERE 子句中作为过滤条件因为执行顺序靠后。若需过滤排名结果必须嵌套子查询 / 临时表。PARTITION BY可以指定多个字段实现多维度分区。排序字段建议建立索引提升大表排名性能。三、实战环境准备直接复制运行我们创建学生成绩表模拟班级、学生、分数场景覆盖全局排名、分组排名、并列排名等所有场景所有案例均基于此表执行。sql-- 创建学生成绩表 CREATE TABLE student_score ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, class_name VARCHAR(20) NOT NULL COMMENT 班级名称, student_name VARCHAR(20) NOT NULL COMMENT 学生姓名, score INT NOT NULL COMMENT 考试分数 ) COMMENT 学生考试成绩表; -- 插入测试数据包含同分、不同班级模拟并列场景 INSERT INTO student_score (class_name, student_name, score) VALUES (一班, 张三, 95), (一班, 李四, 95), (一班, 王五, 88), (一班, 赵六, 80), (二班, 小明, 98), (二班, 小红, 95), (二班, 小刚, 95), (二班, 小丽, 76); -- 基础查询查看原始数据 SELECT * FROM student_score;原始数据说明一班张三、李四同分 95 分二班小明最高分 98小红、小刚同分 95 分存在大量并列分数完美测试三大排名函数差异。四、应用案例及结果分析案例 1全局排名无 PARTITION BY全表一个窗口需求对所有学生按分数从高到低全局排名同时展示三种排名结果直观对比函数差异。sqlSELECT class_name AS 班级, student_name AS 姓名, score AS 分数, -- 唯一行号 ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num, -- 标准排名跳号 RANK() OVER (ORDER BY score DESC) AS rank_num, -- 密集排名不跳号 DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank_num FROM student_score;结果分析ROW_NUMBER所有行序号 1~8 连续不重复即使分数相同序号也依次递增RANK95 分的多名学生并列第 2下一名直接跳到第 5名次跳跃DENSE_RANK95 分学生并列第 2下一名紧跟第 3名次连续无跳跃无PARTITION BY整张表作为一个窗口实现全表全局排名。案例 2分组排名PARTITION BY按班级分区需求每个班级内部单独排名班级之间互不干扰按分数降序展示三种排名。sqlSELECT class_name AS 班级, student_name AS 姓名, score AS 分数, -- 班级内唯一行号 ROW_NUMBER() OVER (PARTITION BY class_name ORDER BY score DESC) AS class_row_num, -- 班级内标准排名 RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS class_rank, -- 班级内密集排名 DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS class_dense_rank FROM student_score;结果分析PARTITION BY class_name将数据拆分为一班、二班两个独立窗口排名在班级内重新从 1 开始一班内部张三、李四 95 分并列三大函数延续各自规则二班同理这是职场分组排名最常用写法例如各部门员工薪资排名、各品类商品销量排名。案例 3经典面试题 - 分组取 TopN取每个班级前三名需求查询每个班级分数前三名的学生窗口函数标准解法高频面试题。解题思路窗口函数不能用于WHERE过滤因此需要嵌套子查询先通过窗口函数生成班级内排名再在外层筛选排名≤3 的数据。sql-- 分组取Top3 完整SQL SELECT * FROM ( SELECT class_name, student_name, score, DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS rank_num FROM student_score ) AS t WHERE t.rank_num 3; -- 筛选前三名结果分析内层子查询完成班级内密集排名外层WHERE过滤出排名前三的学生选用DENSE_RANK原因存在并列分数时不会漏掉同分高分学生若使用ROW_NUMBER会强制截断同分数据根据业务场景灵活选择。案例 4仅使用 ROW_NUMBER 实现分页行号需求给所有学生数据添加连续行号实现简易分页序号替代传统变量行号写法。sqlSELECT ROW_NUMBER() OVER (ORDER BY id) AS 行号, class_name, student_name, score FROM student_score;结果分析ROW_NUMBER天生生成唯一连续序号是数据分页、流水号生成的最优选择。五、注意事项避坑指南WHERE 不能直接使用窗口函数窗口函数执行顺序晚于WHERE直接在WHERE中写排名函数会语法报错。解决方案嵌套子查询、CTE 公用表表达式下篇讲解。排名函数必须搭配 ORDER BY若省略OVER()中的ORDER BY数据无序排名结果无业务意义。合理选择三大排名函数分页、流水号、强制区分顺序 → 优先ROW_NUMBER()常规榜单、赛事排名允许跳号→ 优先RANK()成绩档位、等级、分组 TopN保留同分→ 优先DENSE_RANK()。PARTITION BY 多字段分区规则支持PARTITION BY 字段1,字段2例如PARTITION BY 年级,班级实现多级分区排名。版本限制窗口函数仅MySQL 8.0 及以上版本支持5.7 及以下版本无法使用生产环境需提前确认数据库版本。大表性能优化PARTITION BY和ORDER BY涉及的字段建议建立复合索引避免全表排序导致查询缓慢。区分分区与分组PARTITION BY是窗口分区保留明细GROUP BY是聚合分组合并行切勿混用。六、核心总结窗口函数核心定义基于数据窗口计算保留所有明细行是 MySQL 8.0 标志性新特性主打排名、统计、行间分析。基础语法函数名() OVER(PARTITION BY 分区字段 ORDER BY 排序字段)。三大排名函数核心差异ROW_NUMBER()唯一连续序号无并列适合分页、行号RANK()同分并列名次跳跃适合通用榜单DENSE_RANK()同分并列名次连续适合成绩、档位、分组 TopN。两大核心场景无PARTITION BY全局全表排名有PARTITION BY分组分区排名各组独立计算。经典题型分组取 TopN 必须嵌套子查询过滤排名面试高频考点。使用红线窗口函数不支持直接在WHERE中过滤注意 MySQL 版本兼容。一句话记忆窗口函数存明细OVER 括号定窗口分区排序分两步三大排名各有术行号连续无并列Rank 跳号 Dense 续TopN 查询套子查版本 8.0 别记错。七、练习题附答案思路基于student_score表完成以下练习巩固排名函数用法。题目 1查询所有学生信息使用RANK()函数按分数升序做全局排名。题目 2使用DENSE_RANK()实现查询每个班级分数后两名的学生。题目 3简述ROW_NUMBER()、RANK()、DENSE_RANK()三者在处理并列数据时的区别面试简答题。题目 4判断正误可以直接在 WHERE 子句中使用窗口函数过滤排名并说明原因。参考答案思路题目 1sqlSELECT class_name, student_name, score, RANK() OVER (ORDER BY score ASC) AS global_rank FROM student_score;题目 2sqlSELECT * FROM ( SELECT class_name, student_name, score, DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS rank_num FROM student_score ) t WHERE t.rank_num (SELECT COUNT(*) FROM student_score s WHERE s.class_name t.class_name) - 1; -- 简易写法直接筛选倒数2名按分数升序排名 SELECT * FROM ( SELECT class_name, student_name, score, DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score ASC) AS rank_num FROM student_score ) t WHERE t.rank_num 2;题目 3 参考答案ROW_NUMBER()无论分数是否相同都会生成唯一且连续的序号不存在并列名次RANK()分数相同则名次相同后续名次会跳跃DENSE_RANK()分数相同则名次相同后续名次连续不跳跃。题目 4 参考答案错误。窗口函数的执行顺序晚于 WHERE 子句执行 WHERE 过滤时窗口函数还未计算出结果因此无法直接使用。解决方案是嵌套子查询或者使用 CTE 表达式。