DENSE_RANK更适合“去重并保序”的前三名,因其对相同值分配相同排名且后续不跳号(如两个第1后是第2),而ROW_NUMBER强制唯一编号会破坏并列语义;标准写法为子查询中用DENSE_RANK计算组内排名,外层筛选rnk≤3。

为什么DENSE_RANK比ROW_NUMBER更适合“去重并保序”的前三名
因为 DENSE_RANK 对相同值分配相同排名,且后续排名不跳号(比如两个并列第1后是第2),而 ROW_NUMBER 强制唯一编号,会把并列数据强行拆成第1、第2,破坏业务语义。查“销量前三的员工”,若两人同为最高销量,他们理应都是第1名,而不是一个第1一个第2。
用子查询 + DENSE_RANK实现分组内前三名的标准写法
核心思路:先在子查询(或 CTE)中对每组计算 DENSE_RANK(),再在外层筛选 rnk 。注意必须按业务逻辑明确 <code>ORDER BY 的字段和方向,否则排名无意义。
常见错误现象:Window function is not allowed in WHERE clause —— 不能直接在 WHERE 里用 DENSE_RANK(),必须套一层子查询。
- 按部门分组,取各组薪资最高的前三人(允许并列):
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk
FROM employees
) t
WHERE rnk <= 3;
-
PARTITION BY决定“分组依据”,漏写就变成全表排名 -
ORDER BY必须存在,且方向影响结果(DESC才能拿到“最高”前三) - 若需稳定排序(相同薪资时避免每次结果不同),可追加唯一字段,如
ORDER BY salary DESC, emp_id ASC
与RANK、ROW_NUMBER混用时的关键区别
三者都可用于排名,但语义不同,选错会导致“前三”漏人或多人超限:
-
DENSE_RANK():并列不跳号 → [1,1,2,2,3] ✅ 适合“最多取3个名次”的场景 -
RANK():并列跳号 → [1,1,3,3,5] ❌ 第三名可能直接跳到5,导致实际返回少于3条 -
ROW_NUMBER():强制唯一 → [1,2,3,4,5] ❌ 并列者被拆散,高销量员工可能排到第4被过滤掉
例如某部门四人薪资为 [10k, 10k, 9k, 9k],用 DENSE_RANK 得 [1,1,2,2],全部保留;用 ROW_NUMBER 得 [1,2,3,4],第4人被误剔除。
性能与索引建议:大表慎用,优先考虑覆盖索引
DENSE_RANK 是窗口函数,执行时需对每个 PARTITION BY 组内排序,数据量大时 I/O 和内存开销明显。MySQL 8.0+、PostgreSQL、SQL Server 支持较好,但旧版 MySQL(Unknown function 'DENSE_RANK'。
- 加速关键:为
PARTITION BY和ORDER BY字段建联合索引,例如INDEX(dept, salary) - 若只查单个分组(如
WHERE dept = 'sales'),可去掉PARTITION BY,改用ORDER BY salary DESC LIMIT 3,更轻量 - 注意:PostgreSQL 中
DENSE_RANK() OVER (ORDER BY ...)若没PARTITION BY,就是全表排名,别漏写
真正容易被忽略的是——当分组维度本身有大量空值(如 dept IS NULL 占比高),这些记录会被归为同一组,可能意外拉出数百条“NULL组前三”,需要提前 WHERE dept IS NOT NULL 过滤。

















