DENSE_RANK() 能解决名次断层问题,因其并列时同名次、后续不跳号,如1/1/2/3;而RANK()并列后跳号(1/1/3/4),ROW_NUMBER()无并列(1/2/3/4)。

为什么 DENSE_RANK() 能解决名次断层问题
当你用 RANK() 或 ROW_NUMBER() 排名时,遇到并列就会跳号——比如两个第1名后直接跳到第3名。而 DENSE_RANK() 的设计目标就是“不跳号”:并列只占一个名次,后续名次紧接其后。它不是修修补补,而是从语义上就定义了“稠密排名”。
常见错误是误以为 RANK() 和 DENSE_RANK() 只差一个字母,参数和用法完全一样——确实一样,但语义结果完全不同,且不可互相替代。
DENSE_RANK() 的基本写法和关键参数
它必须配合 OVER() 子句使用,且只接受 ORDER BY(必要),不支持 PARTITION BY 以外的窗口框架(如 ROWS BETWEEN)。排序方向直接影响名次顺序,升序是默认,降序要显式写 DESC。
-
ORDER BY score DESC:分数高的排第1名 -
ORDER BY name ASC:按姓名字典序排,常用于去重后保序 - 不能写
ORDER BY score + 0这类表达式(部分数据库如 MySQL 8.0+ 支持,但 PostgreSQL 不支持,属兼容性雷区)
示例:
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS rank FROM students;
和 RANK()、ROW_NUMBER() 的实际差异对比
三者输入完全相同时,输出名次逻辑不同。最容易踩的坑是:在分页或前端展示时假设“第2页 = LIMIT 10 OFFSET 10”,却没意识到 RANK() 导致实际名次序列稀疏,OFFSET 后可能漏掉整个名次段。
-
ROW_NUMBER():严格按行序编号,无并列,1/2/3/4… -
RANK():并列则同号,后续跳号,1/1/3/4… -
DENSE_RANK():并列则同号,后续不跳号,1/1/2/3…
如果业务要求“前3名只有3个人”,用 ROW_NUMBER();如果要求“分数并列第1名的都算前3名”,就得用 DENSE_RANK()。
注意 NULL 值和排序稳定性
DENSE_RANK() 对 NULL 的处理取决于数据库默认排序行为:NULLS FIRST 还是 NULLS LAST。PostgreSQL 默认 NULLS LAST,MySQL 8.0+ 默认把 NULL 当最小值(即 NULLS FIRST)。这会导致同一条 SQL 在不同库中名次分布不一致。
- 显式声明
ORDER BY score DESC NULLS LAST(PostgreSQL 支持)可提升可移植性 - MySQL 不支持
NULLS修饰符,需用COALESCE(score, -999999)临时兜底(注意别影响业务语义) - 若排序字段存在大量重复值 + NULL 混合,
DENSE_RANK()仍会把所有NULL归为同一组名次——这点常被忽略
平滑不是自动发生的,是靠你控制排序键的确定性与数据库行为的一致性换来的。

















