rank()一定跳号是因为其设计语义为“并列同名次且后续名次跳过被占位数”,如分数[95,95,88]得[1,1,3],反映“多少人排在你前面+1”,非简单序号,故遇并列时下一名次=当前名次+并列行数。

为什么 RANK() 一定跳号
RANK() 跳号不是 bug,是标准行为:它语义上表示「有多少人排在你前面 + 1」,遇到并列时,会把被并列占用的名次一起计入计数。比如分数 [95, 95, 88],结果是 [1, 1, 3]——第 2 名被逻辑预留,直接跳过。
常见错误现象:RANK() OVER (ORDER BY score DESC) 返回 1, 1, 3, 4, 4, 6,误以为数据或排序出错;用 WHERE rk 却只拿到 4 行(因有两个 1 和一个 3),但业务本意是“取分数最高的前三档人”,而非“排名值 ≤ 3 的人”。
- RANK() 适合强调段位断层场景,如奥运奖牌榜、销售红黑榜
- 它不适用于需要连续名次编号的业务,比如分档报表、前端分页展示、Excel 核对
- 别指望加参数或改 WHERE 条件消除跳号——函数设计如此,换函数才是正解
DENSE_RANK() 是唯一能填平断层的替代方案
要让并列后名次紧接(1, 1, 2, 3 而非 1, 1, 3, 4),必须用 DENSE_RANK(),且只需替换函数名,其余语法完全一致。
正确写法示例:DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)
-
PARTITION BY漏写会导致全表排名,不是分组内排名 -
ORDER BY是强制项,不能省略;建议末尾加上唯一列(如emp_id)防排序不稳定 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 支持;MySQL 5.7 及更早版本不支持窗口函数,会报
ERROR 1064 - 别在
WHERE里直接写DENSE_RANK() OVER (...) —— 窗口函数执行晚于 WHERE,必须套子查询或 CTE
WHERE 里不能直接用排名别名,必须嵌套
RANK() 和 DENSE_RANK() 都在 SELECT 阶段计算,晚于 WHERE 和 GROUP BY。所以 WHERE rk 会报错:<code>Unknown column 'rk' in WHERE clause。
正确做法只有两种:
- 子查询:
SELECT * FROM (SELECT *, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk FROM emp) t WHERE t.rnk
- CTE(更清晰):
WITH ranked AS (SELECT *, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk FROM emp) SELECT * FROM ranked WHERE rnk
注意:如果原始数据需先聚合(如按部门算总销售额再排名),必须先在子查询里完成聚合,再对外层结果开窗,不能在同一个 SELECT 里混用 SUM() 和 DENSE_RANK()。
NULL 值和排序稳定性最容易被忽略
ORDER BY score DESC 中,NULL 默认行为因数据库而异:MySQL 把 NULL 当最大值(排最前),PostgreSQL 默认 NULLS LAST,SQL Server 表现不一致。这会导致 NULL 行意外占了一个名次位置,甚至和非空值并列。
显式控制方式:
- PostgreSQL / Oracle:写
ORDER BY score DESC NULLS LAST - MySQL / SQL Server:用
ORDER BY CASE WHEN score IS NULL THEN 1 ELSE 0 END, score DESC - 更稳妥的做法是提前过滤:
WHERE score IS NOT NULL,避免 NULL 干扰排名逻辑
另一个隐形坑:排序字段若无唯一破 ties 机制(如没加 emp_id),相同 score 的行每次执行顺序可能不同,导致分页结果抖动、导出 Excel 后人工核对不上——这不是函数问题,是排序不稳定。










