ROW_NUMBER()实现严格递增组内排名,需用PARTITION BY分组、ORDER BY排序;RANK()并列后跳号,DENSE_RANK()并列后不跳号;窗口函数结果不可在WHERE中直接引用。

用 ROW_NUMBER() 实现严格递增的组内排名
如果你需要每个分组内从 1 开始、不跳号、不重复的序号(比如“第1名、第2名、第3名…”),ROW_NUMBER() 是最直接的选择。它按 ORDER BY 子句严格排序,相同值也会被赋予不同序号。
常见错误是漏写 PARTITION BY,导致全表排序而非分组排序;或者把排序字段写错,排名逻辑和业务预期不符。
-
PARTITION BY指定分组字段,必须明确写出,不能省略 -
ORDER BY决定组内先后顺序,建议显式声明ASC或DESC,避免数据库默认行为差异 - 示例:按部门分组,按薪资降序排,生成部门内排名:
SELECT dept, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank_in_dept FROM employees;
RANK() 和 DENSE_RANK() 的区别在哪
当存在并列值(比如两个员工同为 15000 元薪资)时,RANK() 会跳过后续名次(1, 2, 2, 4),而 DENSE_RANK() 不跳(1, 2, 2, 3)。选哪个取决于业务是否允许“空名次”。
容易踩的坑是混淆两者语义,上线后发现排行榜出现“没有第3名”的情况,运营同学立刻找过来。
-
RANK():并列则占位,后续名次跳过 -
DENSE_RANK():并列不占位,名次连续 - 三者都依赖
OVER (PARTITION BY ... ORDER BY ...)结构,仅函数名不同 - 性能上无显著差异,选择只由语义决定
WHERE 里不能直接用窗口函数结果
像 WHERE rank_in_dept 这种写法会报错:<code>window function is not allowed here。因为窗口函数在逻辑执行顺序中晚于 WHERE,此时还没计算出排名。
正确做法是用子查询或 CTE 把排名先算出来,再过滤:
WITH ranked AS (
SELECT dept, name, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dr
FROM employees
)
SELECT * FROM ranked WHERE dr <= 3;- 不能在
WHERE、GROUP BY、HAVING中直接引用窗口函数 -
ORDER BY可以直接用(因为执行顺序靠后) - CTE 不是必需的,但比嵌套子查询更易读,尤其多层处理时
MySQL 8.0+ 和旧版兼容性问题
MySQL 在 8.0 才正式支持窗口函数;5.7 或更早版本执行 ROW_NUMBER() 会报错 FUNCTION xxx does not exist。别指望加个 IFNULL 就能绕过。
如果必须兼容老版本,得用变量模拟(不稳定,不推荐用于生产),或者把逻辑移到应用层做分组排序。
- 确认 MySQL 版本:
SELECT VERSION(); - PostgreSQL、SQL Server、Oracle、BigQuery 均原生支持,语法一致
- SQLite 3.25+ 支持,但部分发行版未启用,需检查编译选项
实际写的时候,先想清楚“并列要不要占位”“能不能接受全表扫描”“目标数据库版本支不支持”,再选函数和写法。窗口函数本身不难,但嵌在复杂查询里容易漏掉执行顺序或兼容性约束。

















