RANK() 会自然处理并列——相同值获得相同名次,后续名次跳过,这是设计行为而非 bug;其必须配合 OVER 子句使用,支持 PARTITION BY 分组排名,但对 NULL 值排序行为因数据库而异,且性能依赖排序字段索引。

RANK() 会自然处理并列——相同值获得相同名次,后续名次跳过。这不是 bug,是设计行为。想让它“不跳号”或“去重编号”,说明你选错了函数。
为什么 RANK() 一用就跳号?
RANK() 的逻辑就是「按排序位置给名次」:两个最高分都是第 1 名,那第 3 名才是下一个有效名次,中间的 2 被跳过。
常见错觉是“数据断了”,其实是它在按规则工作:
• RANK() → 并列同号、后续跳号(1,1,3,4)
• DENSE_RANK() → 并列同号、后续不跳(1,1,2,3)
• ROW_NUMBER() → 强制唯一、严格递增(1,2,3,4)
别硬调 RANK() 去凑连续编号,先确认业务到底要什么。
RANK() 必须带 OVER,否则直接报错
单独写 RANK() 会触发错误:ERROR: window function RANK requires a window definition。
它不是聚合函数,不能脱离窗口上下文:
• 错误写法:SELECT name, RANK() FROM scores;
• 正确写法:RANK() OVER (ORDER BY score DESC)
• 分组内排名必须显式写 PARTITION BY:RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)
漏掉 PARTITION BY 却想实现部门内排名,结果会变成全表乱排。
NULL 值会让 RANK() 排名偏移,且数据库间行为不一致
ORDER BY score DESC 时,NULL 默认排最前(PostgreSQL/Oracle),但 MySQL 不支持 NULLS LAST,SQL Server 行为又不同。
后果是:含 NULL 的字段参与排名,可能把 NULL 算成“最高分”,导致并列出现在意料之外的位置。
稳妥做法:
• 显式控制:ORDER BY score DESC NULLS LAST(PostgreSQL/Oracle 支持)
• 兼容写法(MySQL/SQL Server):ORDER BY CASE WHEN score IS NULL THEN 1 ELSE 0 END, score DESC
• 更干脆:业务明确 NULL 不参与排名,就先 WHERE score IS NOT NULL 过滤掉。
大数据量下 RANK() 性能容易卡在排序环节
RANK() 是窗口函数,内部会触发全量排序,不依赖 GROUP BY,但非常吃 ORDER BY 字段的索引。
如果没在排序字段建索引,10 万行以上就明显变慢。
优化建议:
• 对常用排序字段(如 score、sales_amount)建索引
• 若只需 Top N,加 LIMIT 10 能大幅减少计算量
• 避免在子查询里嵌套多层 RANK(),尤其跨分区时,执行计划易失控
别只盯着函数怎么写,先看执行计划里有没有 Sort 节点和它的成本占比。
RANK() 天然带跳号、不处理 NULL 的默认行为、以及它对索引的强依赖。

















