RANK() 一定跳号是因为其逻辑为“比当前值大的不同值个数+1”,并列时后续名次跳过被占位数;WHERE不可用RANK()别名,因窗口函数执行晚于WHERE。

RANK() 为什么一定跳号
RANK() 跳号不是 bug,是它计算逻辑决定的:名次 = 「比当前值大的不同值个数 + 1」。两个 95 分并列第 1,是因为前面没人比它高;下一个 92 分前面有两个更高分(95 和 95),所以是 2 + 1 = 3,不是 2。
输入 [100, 95, 95, 92] → 输出 [1, 2, 2, 4];输入 [95, 95, 95, 90] → 输出 [1, 1, 1, 4]。跳号数 = 并列行数 − 1,不是固定跳 1。
- 想“并列即占位、后续必须跳过”,比如正式赛事、合规 Top N 统计,就该用
RANK() - 想“并列但不跳号”,改用
DENSE_RANK() - 想“严格连续编号”,哪怕值相同也不同号,用
ROW_NUMBER()
WHERE 中直接过滤 RANK() 别名会报错
Unknown column 'rk' in WHERE clause 是最常见错误。窗口函数执行顺序晚于 WHERE,别名 rk 此时根本不存在。
错误写法:SELECT *, RANK() OVER (ORDER BY score DESC) AS rk FROM students WHERE rk
正确做法必须套一层:
WITH ranked AS ( SELECT *, RANK() OVER (ORDER BY score DESC) AS rk FROM students ) SELECT * FROM ranked WHERE rk <= 3;
- CTE 比子查询更易读,尤其多层嵌套时
- 别在同一个 SELECT 里混用
RANK()和聚合函数(如AVG(score)),会报错 - 如果只是取 Top N,
LIMIT 3(MySQL/PostgreSQL)或TOP 3(SQL Server)能缓解性能压力
PARTITION BY 分区后 RANK() 的边界容易被忽略
RANK() OVER (PARTITION BY class_id ORDER BY score DESC) 是每个班独立排名,不是全校排名。一个班的第 1 名 ≠ 全校第 1 名。
典型误用:用带 PARTITION BY 的 RANK() 去查“全校前 10”,结果变成“每班各取前 10”。
- 要全校排名,删掉
PARTITION BY,只留ORDER BY - 分区字段类型必须一致,比如
'1'(字符串)和1(数字)会被拆成两组 - 分区键为
NULL时,所有NULL自成一组(MySQL/PostgreSQL 行为一致) - MySQL 8.0+ 强制要求分区字段出现在 SELECT 列表中
ORDER BY 里 NULL 和多字段怎么影响并列判断
并列只由 ORDER BY 最左字段是否相等决定。后面字段只是破 ties(打散顺序),不参与名次判定。
错误写法:RANK() OVER (ORDER BY score DESC, submit_time ASC) → 同分但时间不同,名次全不一样
正确做法:RANK() OVER (ORDER BY score DESC) AS rk,再在外层 ORDER BY score DESC, submit_time ASC 控制展示顺序
-
score字段含NULL时,默认行为不一致:PostgreSQL 默认NULLS LAST,MySQL 不支持该语法且把NULL排最前 - 稳妥写法:
ORDER BY COALESCE(score, 0) DESC或显式加NULLS LAST(PostgreSQL/Oracle) - 别在
ORDER BY里写复杂表达式(如LEN(name)),SQL Server 可能报错
RANK() 看似简单,真正卡住人的往往不是函数怎么写,而是分区边界、NULL 处理、执行顺序这些隐性规则——它们不会报语法错,但会让结果悄悄偏离预期。

















