DENSE_RANK()是窗口函数,生成同值同名、不跳级的连续排名;与RANK()(并列但跳级)和ROW_NUMBER()(强制唯一、不并列)的区别在于是否允许并列及是否保持后续序号连续。

什么是DENSE_RANK,它和RANK、ROW_NUMBER的区别在哪
DENSE_RANK() 是窗口函数,用于生成“同值同名、不跳级”的连续排名。比如薪水为 3000, 2000, 2000, 1000,DENSE_RANK() 排出的是 1, 2, 2, 3;而 RANK() 是 1, 2, 2, 4(跳过3),ROW_NUMBER() 是 1, 2, 3, 4(强行编号)。
关键区别在于:是否允许并列 + 是否保留后续序号的连续性。只有 DENSE_RANK() 同时满足“并列”和“不跳级”两个条件。
-
DENSE_RANK()必须配合OVER()子句,且至少要有ORDER BY - 它不支持
ROWS BETWEEN这类窗口帧定义——排名类函数只依赖排序逻辑,不依赖物理行范围 - 在 MySQL 8.0+、PostgreSQL、Oracle、SQL Server 中都可用;但 SQLite 和旧版 MySQL(
怎么写一个标准的DENSE_RANK查询获取第N高的薪水
假设表叫 Employee,薪水字段是 salary,目标是查“第N高的唯一薪水值”(即去重后取第N个,按降序排)。
正确写法是先用 DENSE_RANK() 对去重后的薪水排序,再过滤出排名等于N的那条:
SELECT DISTINCT salary
FROM (
SELECT salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dr
FROM Employee
) t
WHERE dr = N;
注意这里必须用 DISTINCT salary 套一层子查询,否则原始表中重复薪水会多出冗余行,导致 dr = N 匹配到多条——而题目通常只要一个值(如 LeetCode 177 题)。
- 别直接在原表上跑
DENSE_RANK()然后WHERE dr = N,那样可能返回多行(比如5个人拿同一薪水,刚好排第N名) - 如果业务要求返回所有拿到第N高薪水的员工完整记录,那就去掉外层
DISTINCT,改查Employee表本身,并把DENSE_RANK()放在主查询里 -
ORDER BY salary DESC是关键:升序会把最小值排第一,不符合“第N高”语义
常见错误:为什么DENSE_RANK结果全是1,或者报错
最常遇到两个现象:DENSE_RANK() 返回全1,或提示 Window function is not allowed in WHERE clause。
全1 的原因通常是 OVER() 里漏了 ORDER BY。例如写成 DENSE_RANK() OVER () —— 没有排序依据,数据库就把整张表当作一个未排序组,所有行默认排第1名。
WHERE 中直接引用窗口函数报错,是因为 SQL 执行顺序中,WHERE 在 SELECT 和窗口计算之前执行。你不能在 WHERE 里写 DENSE_RANK() > 2。
- 解决办法:必须用子查询或 CTE 把
DENSE_RANK()结果算出来,再在外层WHERE过滤 - 别在
GROUP BY或聚合查询里混用DENSE_RANK(),除非你明确需要分组内排名(此时加PARTITION BY) - MySQL 8.0 以前版本不支持窗口函数,会直接报语法错误,不是写法问题,是版本硬限制
什么时候该用PARTITION BY配合DENSE_RANK
当你需要“每个分组内独立排名”时才加 PARTITION BY。比如查每个部门中薪水前3的员工,就要按部门分组,再在组内按薪水降序排:
PARTITION BY dept
-
PARTITION BY字段值必须来自当前查询的列,不能是表达式或子查询结果(除非提前计算好) - 多个字段分区写成
PARTITION BY dept, team,等价于先按 dept 分,再按 team 细分 - 如果
PARTITION BY和ORDER BY字段相同(比如都用dept),排名将全部为1——因为每组只有一行满足“排序依据相同”
真正容易被忽略的是:DENSE_RANK 的“不跳级”特性只在排序键完全相同时生效;一旦排序键有细微差异(比如时间戳毫秒不同),即使业务上认为“并列”,函数也会给出不同排名。所以做业务排名前,务必确认排序字段是否已做归一化处理(如 ROUND(salary, 2) 或 DATE(created_at))。











