dense_rank() 是获取第二高工资最可靠的方法,因其能正确处理并列情况;而 max() 嵌套或 offset limit 在多值相同时会漏数据,且 null 和全员同薪场景下易静默丢行,需用 left join 或 union all 补空组。

用 DENSE_RANK() 排名后筛选第2名最可靠
直接用 MAX() 嵌套或 OFFSET 1 LIMIT 1 在多值相同时会漏掉真正第二高的工资。比如某组有工资 [5000, 5000, 4000, 4000, 3000],第二高是 4000,但 MAX() WHERE salary 只能返回一个值,且无法处理并列情况。<code>DENSE_RANK() 恰好按值去重连续编号,4000 就稳稳拿到 2。
实操建议:
- 对每组(如
department_id)按salary DESC计算DENSE_RANK() - 外层只取
rank_num = 2的记录 - 注意:别用
RANK()——它遇到并列会跳号(5000,5000,4000→1,1,3),第二高就没了
SELECT department_id, salary
FROM (
SELECT department_id, salary,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dr
FROM employees
) t
WHERE dr = 2;
MySQL 8.0+ 和 PostgreSQL 直接支持,旧版 MySQL 得绕开
MySQL 5.7 及更早版本不支持窗口函数,硬写 DENSE_RANK() 会报错 ERROR 1064。这时候得用自连接或变量模拟排名,但逻辑变重、易出错。
实操建议:
- 先确认版本:
SELECT VERSION(); - MySQL 5.7 或 MariaDB 10.2 以下:用相关子查询统计“比当前工资高的不同薪资数量”是否等于 1
- 示例条件:
(SELECT COUNT(DISTINCT salary) FROM employees e2 WHERE e2.department_id = e1.department_id AND e2.salary > e1.salary) = 1 - 这个写法在大数据量时性能差,
salary字段必须有复合索引(department_id, salary)
NULL 和空组会导致结果缺失,得提前兜底
如果某部门所有人薪资相同(比如全是 NULL 或全是 8000),DENSE_RANK() 结果里就没有 2,那行就彻底消失——不是报错,而是静默丢数据。
实操建议:
- 加
LEFT JOIN或用UNION ALL补空组(例如先查所有部门,再左连第二高结果) -
salary为NULL时,ORDER BY salary DESC默认排最后,不影响排名逻辑;但若业务要求NULL视为最低,则无需额外处理 - 测试时务必构造单人部门、全员同薪、全员
NULL这三类边界数据
GROUP BY + 子查询容易错在“没去重”,导致重复返回
有人写成 SELECT department_id, MAX(salary) FROM employees WHERE salary ,看起来简洁,但只要同一部门存在多个 <code>4000(第二高),就会返回多行,而你只想要一个代表值。
实操建议:
- 这种写法本质是“找小于最高值的最大值”,它不保证唯一性,也不处理并列第二高的语义
- 如果非要这么写,外层必须套一层
SELECT DISTINCT或加LIMIT 1(但后者跨组失效) - 更稳妥的做法是:先用
GROUP BY department_id+ARRAY_AGG(DISTINCT salary) ORDER BY DESC(PostgreSQL)或GROUP_CONCAT(DISTINCT salary ORDER BY salary DESC)(MySQL),再截取第二项——但这已超出标准 SQL 范畴,可读性和维护性下降
DENSE_RANK(),后者得用 ROW_NUMBER()——别混用。











