rank()输出并列名次且跳号(如[1,2,2,4]),row_number()输出唯一行序(如[1,2,3,4]);二者语义不同:rank()表示真实竞赛名次,row_number()仅标识物理顺序。

直接看结果差异:RANK() 和 ROW_NUMBER() 对同一数据分组排序时,编号逻辑根本不同
别被“都是排名函数”误导——RANK() 模拟真实竞赛名次,ROW_NUMBER() 只管行序。比如成绩 [95, 88, 88, 76] 降序排:RANK() 输出 [1, 2, 2, 4](两个第2名,跳过3);ROW_NUMBER() 输出 [1, 2, 3, 4](强行唯一,不管值是否重复)。业务要的是“谁并列第2”,还是“取第2条记录”,决定了该用哪个。
分组内排名写错 PARTITION BY 就全乱了
常见错误是把分组字段写错,比如本该按 dept_id 分组却写了 city,结果变成“每个城市的员工在各自部门里排名”,语义完全失真。更隐蔽的问题是字段含义模糊:如果表里有 region 和 region_code,而业务口径实际指行政大区,但你用了编码字段,分区粒度就细到无法对齐业务逻辑。
- 检查
PARTITION BY字段的基数:若每行都不同(如用id),窗口函数退化为全表排序,性能陡降 - 确认字段是否允许 NULL:MySQL 中
PARTITION BY col遇到 NULL 会把所有 NULL 值归为同一组,可能意外合并不该合并的数据 - 别名不能用于
PARTITION BY:必须用原始列名或表达式,PARTITION BY dept_name不能写成PARTITION BY dname
WHERE 条件里直接写窗口函数会报错
RANK() OVER (...) = 1 或 ROW_NUMBER() OVER (...) = 1 这类写法在 WHERE 子句中直接出现,一定会报语法错误。因为窗口函数在 SQL 执行顺序中晚于 WHERE,此时还没计算出编号。
- 正确做法只有两种:用 CTE 先算号,再外层过滤;或嵌套子查询
- 错误示例:
SELECT * FROM sales WHERE RANK() OVER (PARTITION BY region ORDER BY amount DESC) = 1 - 正确示例:
WITH ranked AS (SELECT *, RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rk FROM sales) SELECT * FROM ranked WHERE rk = 1 - 如果只取 Top 1 且不要并列,
ROW_NUMBER()+WHERE rn = 1更安全,不会因并列导致多返回
空值和排序方向不一致会悄悄改变排名顺序
默认情况下,ORDER BY score DESC 遇到 NULL,多数数据库(如 PostgreSQL、Oracle)把 NULL 当作最大值,排在最前;MySQL 8.0 默认当最小值。这会导致 NULL 成绩被排成第1名,和业务预期严重冲突。
- 显式控制空值位置:
ORDER BY score DESC NULLS LAST(PostgreSQL/Oracle 支持)或ORDER BY IFNULL(score, 0) DESC(MySQL) - 升序时也一样危险:
ORDER BY create_time ASC若有 NULL,可能把未填写时间的脏数据排在第一条 - 别依赖数据库默认行为,只要字段可能为空,就必须声明
NULLS FIRST或NULLS LAST
真正容易被忽略的是:排名差异不是函数写错了,而是你没想清楚“这个数字到底要用来做什么”。RANK() 返回的不是行号,是名次槽位;ROW_NUMBER() 返回的不是名次,是物理位置索引。下游一接聚合或分页,差一个语义理解,结果就偏了。










