DENSE_RANK()是查第N高薪水的首选,因其对重复值赋予相同名次且不跳号,如[100,100,90]中第2高为90;需配合ORDER BY salary DESC、子查询过滤rnk=N,并排除NULL。

为什么 DENSE_RANK() 是查第N高薪水的首选
因为重复薪水必须算同一“名次”,且名次不能跳过——比如 [100, 100, 90] 中第2高薪水是 90,不是空缺。用 RANK() 会跳过名次(两个100后直接跳到3),ROW_NUMBER() 则强行编号(100→1, 100→2, 90→3),都不符合“第N高”的业务语义。
实操建议:
-
DENSE_RANK()必须配合ORDER BY salary DESC,升序或不写会得到最低值 - 窗口函数不能直接在
WHERE中过滤排名,得套一层子查询或 CTE - MySQL 8.0+、PostgreSQL、SQL Server 2017+、Oracle 都支持;MySQL 5.7 及更早版本不支持,得用变量模拟
怎么写一个能返回第N高薪水的可复用SQL
核心结构是:子查询中计算 DENSE_RANK(),外层筛选 rnk = N,再用 SELECT DISTINCT 或 LIMIT 1 防止重复行干扰结果。
常见错误现象:
- 没去重导致返回多行(比如多个员工并列第N高)
- 没处理 N 超出实际名次数的情况(如表里只有3种薪水却查第5高),结果为空——这是正常行为,不是bug
- 在 MySQL 中用了
OFFSET+LIMIT但没加ORDER BY,排序未定义,结果随机
简短示例(查第2高薪水):
SELECT DISTINCT salary FROM ( SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM Employee ) ranked WHERE rnk = 2;
MySQL 5.7 怎么绕过不支持窗口函数的限制
只能靠用户变量模拟排名逻辑,但要注意执行顺序不可靠,必须用子查询强制排序。
关键点:
- 变量初始化必须和排序数据在同一查询中完成,不能依赖外部
SET -
@rank := IF(@prev = salary, @rank, @rank + 1)是核心逻辑,@prev要同步更新 - 性能差:全表扫描 + 变量逐行计算,大数据量时明显变慢
- MySQL 8.0 升级后应立刻迁移到
DENSE_RANK(),变量方案仅作兼容兜底
简短示例(MySQL 5.7):
SELECT salary
FROM (
SELECT salary,
@rank := IF(@prev = salary, @rank, @rank + 1) AS rnk,
@prev := salary
FROM Employee,
(SELECT @rank := 0, @prev := NULL) init
ORDER BY salary DESC
) t
WHERE rnk = 2
LIMIT 1;
NULL 和重复数据对 DENSE_RANK() 的影响
salary 字段含 NULL 时,ORDER BY salary DESC 默认把 NULL 排在最前(MySQL/PostgreSQL)或最后(SQL Server),导致 DENSE_RANK() 把 NULL 算作最高或最低名次——这通常不符合“薪水”业务含义。
实操建议:
- 查第N高薪水前,显式排除
NULL:WHERE salary IS NOT NULL - 不要依赖数据库默认的
NULL排序行为,不同系统差异大 - 如果业务允许
NULL表示“无薪”,应在应用层单独处理,而不是让窗口函数参与排名
容易被忽略的是:哪怕表结构定义了 NOT NULL,ETL 过程或历史数据仍可能混入 NULL,上线前最好验证下 COUNT(*) 和 COUNT(salary) 是否一致。










