MySQL 窗口函数怎么给每组记录编号后分页
来源:17golang原创
时间:2026-09-08 03:44:25 395浏览 收藏
报表里经常要做这样的分页:每个客户只看自己的订单,第 2 页取组内第 11 到 20 条;或者每个部门只保留金额最高的前 3 条。关键不是给整张结果集编号,而是先用 PARTITION BY 划分组,再用窗口函数产生组内编号,最后在外层查询过滤编号。
最稳妥的写法是ROW_NUMBER() OVER (PARTITION BY 分组列 ORDER BY 业务排序列, 唯一键),把结果放进 CTE 或派生表,再用WHERE rn BETWEEN 起始行 AND 结束行分页。排序列相同的记录必须补一个唯一键,否则翻页顺序可能漂移。
PARTITION BY决定“每组重新编号”,不会减少结果行。ROW_NUMBER适合每组严格取固定条数;并列展示要考虑RANK或DENSE_RANK。- 窗口编号生成后再由外层筛选,分页排序要和编号排序保持一致。
为什么要先编号再分页
GROUP BY 更适合把多行聚合成一行,而这里仍要保留每一条订单,只是想知道它在所属客户中的位置。窗口函数正好是在保留明细行的同时计算相关行。MySQL 手册把 ROW_NUMBER() 定义为当前行在分区中的编号,把 RANK() 定义为允许跳号的排名,把 DENSE_RANK() 定义为不跳号的排名。
假设表为 customer_orders,需要对已支付订单按创建时间倒序排列。只写 ORDER BY created_at DESC 不够:同一时刻可能有多笔订单,数据库可以在这些并列值之间采用不同顺序。把主键 order_id 作为第二排序键,才能让第 11 条和第 12 条有稳定边界。

ROW_NUMBER、RANK 和 DENSE_RANK 怎么选
三者都可以写在同一个窗口定义里,但“并列”处理方式不同。下面的选择表比死记函数名更实用:
| 函数 | 相同排序值 | 适合的结果 |
|---|---|---|
ROW_NUMBER() | 仍然逐行编号 | 每组精确取 10 条、组内明细分页 |
RANK() | 并列同名次,后面跳号 | 排行榜需要体现名次间隔 |
DENSE_RANK() | 并列同名次,后面不跳号 | 取每组前 3 个金额档位 |
如果业务说“每组前 3 条”,通常选 ROW_NUMBER;如果说“每组前 3 名,最后一名允许并列”,则要确认是允许结果超过 3 行,常见写法是 RANK 或 DENSE_RANK。不能只看函数返回的数字,还要先确定产品对并列记录的定义。
用 CTE 写出可控的组内分页 SQL
窗口函数产生的别名不能在同一层的 WHERE 中直接使用,因此先放进 CTE,再由外层筛选。下面示例取每个客户组内第 11 到 20 条已支付订单:
WITH ranked_orders AS (
SELECT
order_id,
customer_id,
amount,
created_at,
-- 先按客户分组,再用主键稳定打破相同时间
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY created_at DESC, order_id DESC
) AS rn
FROM customer_orders
WHERE status = 'paid'
)
SELECT order_id, customer_id, amount, created_at, rn
FROM ranked_orders
-- 外层才按组内编号截取第 11 到 20 条
WHERE rn BETWEEN 11 AND 20
ORDER BY customer_id, rn;
这里的 WHERE status = 'paid' 在编号之前执行,所以编号只针对已支付订单。如果把状态条件放到最外层,未支付订单会先占用行号,得到的“已支付第 11 条”就会偏移。最终的 ORDER BY customer_id, rn 也要和阅读顺序一致,避免查询结果看起来跨组跳动。
如果分页参数是第 page 页、每页 page_size 条,可以在应用层计算 start = (page - 1) * page_size + 1 和 end = page * page_size,再绑定为两个参数。不要把用户输入直接拼接进 SQL;边界还应限制为正数,并处理页码超过组内总行数的空结果。

常见问题
为什么不能直接写 WHERE rn
rn 是窗口表达式在查询结果阶段产生的别名,同层 WHERE 不能直接引用它。使用 CTE 或派生表包一层,把编号变成外层可过滤的列。
排序字段相同会不会导致分页重复或漏数据?
可能会。给业务排序字段补上唯一键,并在编号层和最终展示层使用同一套排序规则,才能让页边界稳定。
MySQL 5.7 能否直接使用这套写法?
不能把它当成兼容写法。本文依赖 MySQL 8.0 及更高版本提供的窗口函数和 CTE;旧版本需要升级,或改用更难维护的变量方案。
把“分组”“组内顺序”“分页边界”拆成三个明确层次后,这类 SQL 就不再是给结果集临时编号的小技巧,而是一条可解释、可维护的查询结构。生产环境还应结合实际数据量查看执行计划,重点关注分区和排序带来的临时表与排序成本。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习