dense_rank()能填平名次断层,因其语义为“并列只占一个名次位、后续名次紧接”,对分数100、95、95、90返回1、2、2、3,而非rank()的1、2、2、4,从而保证排名连续不跳空。

为什么 DENSE_RANK() 能填平名次断层
断层不是数据错了,是排名函数选错了。比如分数为 100, 95, 95, 90,RANK() 给出 1, 2, 2, 4——第3名直接消失;而 DENSE_RANK() 输出 1, 2, 2, 3,名次连续不跳空。它不“修复”数据,而是从语义上定义“稠密排名”:并列只占一个名次位,后续名次紧接其后。
常见误操作是把 RANK() 和 DENSE_RANK() 当成可互换的同款函数——参数写法一样,结果却差一个逻辑层级。一旦下游依赖名次做分档(如“前3档”)、分页(LIMIT 10 OFFSET 20)或 Excel 核对,RANK() 的跳号就会漏掉整段数据。
DENSE_RANK() 必须配合 OVER(ORDER BY ...) 使用
DENSE_RANK() 是窗口函数,不能单独用,也不能在 WHERE 或 GROUP BY 中直接引用别名。它只接受 OVER() 子句,且 ORDER BY 是必需项,PARTITION BY 是可选项。
- 全表连续排名:
DENSE_RANK() OVER (ORDER BY score DESC) - 按部门内排名:
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) - 多字段排序保稳定(防重复值导致序号漂移):
ORDER BY score DESC, emp_id,其中emp_id是唯一列,仅用于破 ties,不改变并列逻辑 - 别写
ORDER BY 2(SQL Server 不支持),也别用表达式如score + 0(PostgreSQL 不认,MySQL 8.0+ 虽支持但属兼容性雷区)
和 RANK()、ROW_NUMBER() 混用时最容易翻车的三个点
三者语法几乎一样,但行为差异会直接破坏业务逻辑:
- 用
RANK()写WHERE rank 查“前3名”,实际可能只返回2行(两人并列第1,下一个是第3);换成 <code>DENSE_RANK()后,同样条件可能返回4行(1,1,2,3),数量变多——这不是 bug,是语义正确性的体现 -
ROW_NUMBER()根本不处理并列,哪怕两行score完全相同,也会硬编出1, 2。想保全所有并列者,它不能替代DENSE_RANK() - 在 MySQL 8.0+ 或 PostgreSQL 中可用,但旧版 MySQL(5.7 及更早)压根不支持窗口函数,执行会报错
ERROR 1064 (42000): You have an error in your SQL syntax
分页、导出、报表场景下必须检查的细节
断层问题在展示层最敏感,光函数选对还不够:
- 前端分页依赖名次值计算 offset 时,
DENSE_RANK()的连续性让页边界更“实”,但得同步确认ORDER BY是否含唯一列——否则相同分数的行每次执行顺序可能不同,导致分页结果抖动 - 导出 Excel 后人工核对发现“排名数字变小了”,大概率是原来用
RANK()导致名次膨胀(比如最高只到 100,但因跳号实际占了 150 个名次位),换DENSE_RANK()后压缩到了 100 以内 - NULL 值参与排序时,不同数据库默认行为不一致:PostgreSQL 把
NULL排最前,MySQL 可能排最后。若排序字段可能为空,显式加NULLS LAST(PostgreSQL/Oracle 支持)或用COALESCE(score, -1)做兜底
真正容易被忽略的是:DENSE_RANK() 解决的是名次序列的连续性,不是数据本身的去重或合并。每一行仍独立存在,只是名次不再留空——这个前提没立住,后面所有分档、筛选、导出都会跑偏。











