row_number() 实现组内连续排名需配合 partition by 分组和确定性 order by(如 salary desc, id asc),确保每行唯一序号、不跳号不并列,避免漏写 partition by 或使用非确定性排序字段。

用 ROW_NUMBER() 实现组内连续排名
需要每组独立编号、且相同值也要区分先后(比如按时间戳排序),ROW_NUMBER() 是最直接的选择。它不跳号、不并列,保证每行唯一序号。
常见错误是漏写 PARTITION BY,导致全表排序而非分组;或 ORDER BY 里用了非确定性字段(如无索引的 name),使结果不稳定。
- 必须配合
PARTITION BY指定分组字段,例如PARTITION BY department_id -
ORDER BY推荐包含主键或时间字段,避免重复值引发执行计划抖动 - 示例:获取每个部门薪资最高的前3人:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY department_id ORDER BY salary DESC, id ASC ) AS rn FROM employees ) t WHERE t.rn
RANK() 和 DENSE_RANK() 的区别与选型
当组内有相同值(比如多人同薪),RANK() 会跳过后续名次,DENSE_RANK() 则连续计数。选哪个取决于业务是否允许“并列第2名之后是第4名”这种逻辑。
典型场景:排行榜展示时用户更接受 DENSE_RANK()(并列第2后就是第3);而考核系统可能要求严格名次间隔,用 RANK()。
-
RANK():值相同时名次相同,后续名次跳过,如1,2,2,4 -
DENSE_RANK():值相同时名次相同,后续名次不跳,如1,2,2,3 - 三者性能差异极小,但
ORDER BY字段越多,排序开销越明显
WHERE 中不能直接用窗口函数结果
写 WHERE rn 会报错 <code>column "rn" does not exist,因为窗口函数在 WHERE 之后执行。这是 SQL 执行顺序导致的硬性限制。
解决方式只有两种:嵌套子查询(上例已展示),或用 CTE。CTE 更易读,但某些旧版 MySQL 不支持;子查询兼容性更好。
- 不能写:
SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t WHERE rn - 必须写成子查询或 CTE,且
OVER子句里的ORDER BY会影响最终结果顺序 - 如果只想要前 N 行但不关心具体名次,
LIMIT或TOP更快,但它不支持分组——这点常被忽略
MySQL 8.0+ 和 PostgreSQL 的语法一致性
只要版本达标(MySQL ≥ 8.0,PostgreSQL ≥ 8.4),ROW_NUMBER()、RANK()、DENSE_RANK() 语法完全一致,参数和行为无差异。真正容易出问题的是旧版 MySQL(5.7 及之前)——它根本不支持窗口函数。
如果必须兼容 MySQL 5.7,只能用自连接或变量模拟,但性能差、逻辑复杂,且在高并发下容易出错。
- 确认版本:运行
SELECT VERSION(); - PostgreSQL 默认支持,无需额外配置
- MySQL 5.7 用户不要强行移植窗口函数写法,重构成本远高于升级数据库
实际用的时候,先想清楚“相同值要不要并列”“是否必须严格分组”“数据库版本卡在哪”,再选函数和结构。窗口函数本身不难,难的是把业务语义准确映射到 SQL 执行逻辑里。











