rank() 会跳号是设计使然,适用于并列即占位、后续名次必须跳过的场景,如正式赛事排名、合规性 top n 统计;误用会导致 where rk 查询异常。

RANK() 会跳号,不是 bug,是设计使然。它只适合“并列即占位、后续名次必须跳过”的业务场景,比如正式赛事排名、合规性 Top N 统计。用错地方,查 WHERE rk 会漏数据,分档会断层。
为什么 RANK() 一定跳号?
它计算的是「当前值前面有多少个更优值 + 1」,而不是「第几个出现」。两个 95 分并列第 1 名,说明前面没人比它高;下一个 92 分前面有两个更高分(95 和 95),所以名次是 2 + 1 = 3,不是 2。
- 输入:100, 95, 95, 92 →
RANK()输出:1, 2, 2, 4 - 输入:95, 95, 95, 90 → 输出:1, 1, 1, 4
- 跳号数 = 并列行数 − 1,不是固定跳 1
RANK() 在 WHERE 中直接过滤一定会报错
column "rk" does not exist 是最常见报错。因为窗口函数在 SQL 执行顺序中晚于 WHERE,别名 rk 此时还不存在。
- 错误写法:
SELECT *, RANK() OVER (ORDER BY score DESC) AS rk FROM students WHERE rk - 正确写法:必须套一层子查询或 CTE
- 推荐 CTE 写法(可读性强):
WITH ranked AS ( SELECT *, RANK() OVER (ORDER BY score DESC) AS rk FROM students ) SELECT * FROM ranked WHERE rk
多条件排序时,ORDER BY 写错就废掉并列逻辑
并列只由 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控制展示顺序 - 如果主排序字段含
NULL,默认行为不一致(PostgreSQL 默认NULLS LAST,MySQL 不支持该语法),建议显式处理:ORDER BY COALESCE(score, 0) DESC
PARTITION BY 分区后,RANK() 的边界容易被忽略
分区是独立重置的开关,但很多人忘了:RANK() 只在每个分区内排序,跨区不比。一个班级里第 1 名,不等于全年级第 1 名。
- 典型误用:
RANK() OVER (PARTITION BY class_id ORDER BY score DESC)用于“全校前 10”,结果其实是每个班各取前 10 - 要全校排名,就不能写
PARTITION BY;要班内排名,才加 - 分区字段类型必须一致,混用字符串和数字(如
'1'和1)会导致同一逻辑组被拆成多个分区 - 分区键为
NULL时,各NULL自成一组(MySQL/PostgreSQL 行为一致)
真正难的不是怎么写 RANK(),而是判断业务是否真的需要跳号——多数内部系统其实更需要 DENSE_RANK() 或 ROW_NUMBER(),硬套 RANK() 反而埋坑。











