rank()和dense_rank()跳号因其按“组内排序位置”而非“名次序号”计算:rank()遇并列跳过后续数字,dense_rank()只跳重复值本身;二者均不生成严格连续编号。

为什么 RANK() 和 DENSE_RANK() 会跳过后续排名?
因为它们本质是按「组内排序位置」计数,不是按「名次序号」生成。当出现并列时,RANK() 直接跳过被并列占用的后续数字(比如两个第1名后,下一个是第3名);DENSE_RANK() 虽不跳名次,但依然按“当前有多少个更优值”来算——它只跳过重复值本身,不跳数字序列。
常见错误现象:RANK() 在分页或前端展示时突然缺号,让人误以为数据丢失;DENSE_RANK() 在需要严格连续编号(如奖牌榜前三必须是1/2/3)时仍不够用。
-
RANK():适用于强调“击败了多少人”,例如“超过95%的用户”这种相对定位场景 -
DENSE_RANK():适合需要紧凑名次、但允许并列影响后续编号的业务,比如内部绩效档位(S/A/B 并列A就都算第二档) - 两者都不接受
ORDER BY外的其他参数,窗口定义必须明确,漏写PARTITION BY可能导致全表误排
如何用 ROW_NUMBER() 替代实现不跳号的并列排名?
真正不跳号的“并列即同号、后续紧连”逻辑,ROW_NUMBER() 单独做不到,但可以配合子查询模拟:先用 DENSE_RANK() 定义名次组,再对每组内用 ROW_NUMBER() 标记原始顺序,最后取每组最小 ROW_NUMBER() 作为该组统一编号。
更实用的做法是改用变量或 CTE 构造伪排名,但注意 MySQL 8.0+、PostgreSQL、SQL Server 都支持窗口函数嵌套,可直接写:
SELECT id, score,
(SELECT MIN(rn) FROM (
SELECT id, score, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM scores
) t2 WHERE t2.score >= scores.score) AS rank_no_gap
FROM scores;
性能提示:子查询方式在大数据量时可能变慢;若只需最终 Top N,加 LIMIT 或 TOP 能显著缓解。
ORDER BY 中 NULL 值怎么影响 RANK() 结果?
RANK() 和 DENSE_RANK() 默认把 NULL 当作最大值处理(即 ORDER BY col DESC 时 NULL 排最前),这常导致并列名次出现在意料之外的位置。
- 显式控制:
ORDER BY col DESC NULLS LAST(PostgreSQL、Oracle、SQL:2003 标准) - 兼容写法(MySQL/SQL Server):
ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col DESC - 如果业务要求 NULL 不参与排名,应在
WHERE中提前过滤,否则它们仍会占据一个排名位置
分区后 RANK() 的边界行为容易踩哪些坑?
PARTITION BY 是独立重置排名的开关,但很多人忽略它和 ORDER BY 的作用范围关系——RANK() 只在每个分区内排序,跨区不比较。
典型问题:按部门分组排名销售员,结果发现 A 部门第1名销售额比 B 部门第1名还低,误以为函数出错。其实这是设计使然。
- 确保
PARTITION BY字段有明确业务边界,避免用高基数字段(如 user_id)导致大量单行分区 - 若需“全局排序 + 分组标识”,得先用全局
RANK()计算总名次,再用CASE WHEN关联到对应分组 -
ORDER BY表达式中不能引用窗口函数别名,必须重复写表达式,否则报错invalid reference to FROM-clause entry
并列处理从来不是纯函数选择问题,而是业务语义映射问题。RANK() 和 DENSE_RANK() 各自跳号逻辑背后,其实是两种不同的“胜出者定义”——前者数“被你压在下面的人”,后者数“比你强的等级数”。选错函数,往往是因为没先想清楚这句话该怎么说。











