dense_rank()用于并列不跳号排名,因其语义为并列只占一个名次、后续名次紧接,满足并列、连续、不跳空三条件;必须搭配over(order by),不可在where中引用其别名。

用 RANK() 还是 DENSE_RANK()?先看并列后要跳几号
分组排名遇到相同值时,要不要“跳过后续名次”,直接决定函数选法。RANK() 会跳(比如两个第1名后直接第3名),DENSE_RANK() 不跳(两个第1名后是第2名)。别硬记,就看业务里“第2名”能不能和“第1名”并存——能,就用 DENSE_RANK();不能,比如体育比赛奖牌榜,第二名必须唯一,就得用 RANK()。
常见错误:在需要连续名次的报表里误用 RANK(),导出数据后发现“第1、第1、第3”让人困惑。反过来,在必须体现实际名次层级(如按销售额分档)时用了 DENSE_RANK(),结果本该空出的档位被填满,逻辑错位。
ROW_NUMBER() 为什么不算“处理并列”?
ROW_NUMBER() 本质是编号,不是排名。它强制给每行分配唯一序号,哪怕 ORDER BY 字段完全一样,也会靠隐式排序(如物理存储顺序或执行计划)强行拆开。所以它根本不管“并列”,也解决不了“相同值同名次”的需求。
使用场景举例:你只是想给每条订单加个序号(不分组、不比较值),ROW_NUMBER() 合适;但你要告诉运营“这5个用户并列TOP3”,就不能用它。
容易踩的坑:
- 误以为加了
PARTITION BY就自动处理并列——其实并列行为只由窗口函数类型决定,ROW_NUMBER()加了分组也照样一人一号 - 在
ORDER BY里混入无关字段试图“稳定排序”,反而干扰真实业务排序逻辑
分组内并列排名的完整写法(含 PARTITION BY)
真正落地时,几乎总要结合 PARTITION BY 做分组内排名。例如按部门算员工薪资排名:
SELECT dept, name, salary, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank_in_dept FROM employees;
注意三点:
-
PARTITION BY dept是分组依据,不是排序依据;ORDER BY salary DESC才决定谁排前面 - 如果部门内多人薪资相同,
RANK()和DENSE_RANK()的输出差异立刻可见,建议先用小样本验证 - MySQL 8.0+、PostgreSQL、SQL Server 都支持;但旧版 MySQL(
性能与 NULL 值怎么影响排名结果?
排名函数对 NULL 的处理默认是“最大值”(即 ORDER BY ... DESC 时 NULL 排最前),这点常被忽略。如果薪资字段有 NULL,它可能被排成第1名,而你本意是“未知不参与排名”。解决办法是显式控制:ORDER BY salary DESC NULLS LAST(PostgreSQL/Oracle 支持),或用 CASE 转换:ORDER BY CASE WHEN salary IS NULL THEN 1 ELSE 0 END, salary DESC。
性能方面:窗口函数本身不索引友好,大数据量时 ORDER BY 字段最好有索引;若同时做多层嵌套(如排名后再过滤 top 3),优先用 QUALIFY(BigQuery/Trino)或子查询,避免重复计算。
复杂点在于:并列逻辑看似简单,但一旦叠加分组、NULL、跨库兼容、前端展示格式(比如“第1名(并列2人)”),每个环节都可能漏掉一环。尤其别在没确认数据库版本的情况下,直接把 DENSE_RANK() 语句扔进生产脚本。










