查每组第二高工资必须用 DENSE_RANK(),因其对并列值同名且后续紧接(如1,1,2),而ROW_NUMBER()和RANK()无法正确映射“第N高”语义;需两层嵌套结构实现,且须排除NULL工资。

用 DENSE_RANK() 而不是 RANK() 或 ROW_NUMBER()
直接结论:查每组第二高工资,必须用 DENSE_RANK(),否则遇到并列工资时结果会错。比如某部门有三人薪资为 15000、15000、12000,第二高实际是 12000,但 ROW_NUMBER() 会给两个 15000 分配 1 和 2,把 12000 排成第 3,漏掉真实第二高。
原因在于:ROW_NUMBER() 强制编号不跳号,RANK() 跳号但重复值后空位太多(如 1,1,3),只有 DENSE_RANK() 保证重复值同名且后续紧接(1,1,2),正好对应“第 N 高”的语义。
- 按部门分组:用
PARTITION BY department - 按工资降序:用
ORDER BY salary DESC - 筛选 rank = 2:WHERE 子句或外层过滤,不能在窗口内加条件
写法必须两层嵌套:先算 rank,再过滤
窗口函数不能直接出现在 WHERE 中——SQL 执行顺序是 WHERE 先于窗口计算。所以必须用子查询或 CTE 先生成 rank 列,再在外层筛选 rank_num = 2。
常见错误是写成 WHERE DENSE_RANK() OVER (...) = 2,会报错 Window function is not allowed in WHERE clause。
- CTE 写法更清晰:
WITH ranked AS ( SELECT department, salary, DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk FROM employees ) SELECT department, salary FROM ranked WHERE rnk = 2; - 若某组不足两人(如只有 1 人),该部门无结果——符合“第二高”定义,不用额外补 NULL
- 注意:
salary是原始值,不是去重后的;DENSE_RANK()天然处理重复,无需先DISTINCT
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但旧版 MySQL 不行
如果你用的是 MySQL 5.7 或更早版本,DENSE_RANK() 直接报错 FUNCTION xxx.DENSE_RANK does not exist。没有窗口函数,只能用关联子查询或变量模拟,逻辑复杂且易出错。
- 替代方案(仅限无窗口函数环境):
SELECT DISTINCT e1.department, e1.salary FROM employees e1 WHERE 1 = ( SELECT COUNT(DISTINCT e2.salary) FROM employees e2 WHERE e2.department = e1.department AND e2.salary > e1.salary ); - 这个写法依赖
COUNT(DISTINCT ...)计算“比当前工资高的不同薪资个数”,等于 1 即为第二高 - 性能差:对每行都跑一次子查询,数据量稍大就明显变慢
- 别用用户变量(如
@rank := @rank + 1),MySQL 8.0 已明确不保证执行顺序,结果不可靠
NULL 工资和空组要提前判断
salary 为 NULL 时,ORDER BY salary DESC 默认把 NULL 排在最前(MySQL/PostgreSQL)或最后(SQL Server),导致 DENSE_RANK() 把 NULL 算作第 1 名——这显然不是你想要的“第二高工资”。
- 显式排除 NULL:
WHERE salary IS NOT NULL放在 CTE 内部或最外层 - 如果某部门所有人 salary 都为 NULL,则该部门无输出,符合预期
- 空部门(无员工)自然不出现在结果中,无需额外处理
- 不要依赖
COALESCE(salary, 0)替代 NULL——工资真为 0 和缺失是两回事
DENSE_RANK(),且必须两层结构——其余都是细节问题。

















