DENSE_RANK()是唯一能实现“并列不跳号”排名的窗口函数,如95分并列第1名后紧接92分第2名;须配合PARTITION BY分组和ORDER BY排序,且不能预先去重或聚合。

DENSE_RANK() 是 PostgreSQL 里唯一能天然实现“并列不跳号”排名的窗口函数,只要写对 PARTITION BY 和 ORDER BY,基本不会出错。
为什么必须用 DENSE_RANK() 而不是 RANK()
当两个员工绩效同为 95 分,下一个最低是 92 分时:RANK() 会给出 1, 1, 3(中间跳掉 2),而 DENSE_RANK() 给出 1, 1, 2——这才是业务上想要的“第 2 名”。
常见误判是以为 RANK() 更“标准”,其实它适合运动会颁奖(金牌并列后直接发铜牌),但不适合绩效、考试、销售榜这类需要连续档位的场景。
- 用
RANK()做部门 Top 3 筛选,可能漏掉实际排第 2 档的人(因为名次跳成了 4) -
DENSE_RANK()的名次序列永远是 1, 1, 2, 2, 3, 4… 没空洞 - 如果数据库版本低于 PostgreSQL 8.4,该函数不可用(但 2026 年基本不用考虑)
PARTITION BY dept_id 忘了就全公司一起排
没加 PARTITION BY,DENSE_RANK() 就变成全局排名,哪怕你本意是“每个部门独立排”。
正确写法:
SELECT emp_id, dept_id, score, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY score DESC) AS rank_in_dept FROM employees;
错误写法(没分区):
SELECT emp_id, dept_id, score, DENSE_RANK() OVER (ORDER BY score DESC) AS rank_in_dept -- 全公司混排! FROM employees;
- MySQL 8.0+、PostgreSQL、SQL Server 都支持
PARTITION BY,语法一致 - 如果
dept_id是 NULL,对应行会被分到一个隐式分区,通常要先WHERE dept_id IS NOT NULL - 排序字段含 NULL 时,PostgreSQL 默认
NULLS FIRST,可能把空绩效排第一;加NULLS LAST更稳妥:ORDER BY score DESC NULLS LAST
和 ROW_NUMBER() 混用会导致逻辑断裂
有人想“先去重再排名”,在子查询里套 ROW_NUMBER(),结果破坏了并列基础——ROW_NUMBER() 强制每行唯一编号,后续再套 DENSE_RANK() 已无意义。
真正需要并列不跳号,就必须让 DENSE_RANK() 直接作用于原始明细行,不能提前聚合或去重。
- 不要在外部用
GROUP BY后再套窗口函数:窗口计算发生在聚合前,GROUP BY后原始行已丢失 - 做 Top N 且允许并列时,必须用
DENSE_RANK() ,不能用 <code>ROW_NUMBER() (否则并列者只留一个) - BI 工具下钻不稳定?大概率是前端把
DENSE_RANK()当普通字段处理了,建议在视图层固化排名,而非依赖动态计算
最易被忽略的一点:DENSE_RANK() 的“不跳号”只针对排序值相同的情况;如果排序字段本身精度不够(比如用 ROUND(salary, -3) 后再排),看起来并列,其实是人为制造的“假并列”,这时排名连续性就失去了业务依据。

















