正确做法是使用相关子查询或窗口函数:相关子查询需在子查询中关联外层部门字段,如e2.department_id = e1.department_id;窗口函数推荐用rank() over (partition by department_id order by salary desc)处理并列情况。

子查询必须关联部门字段才能避免全表误匹配
直接用 SELECT * FROM employees WHERE salary = (SELECT MAX(salary) FROM employees) 会返回所有部门中全局最高薪员工,不是每个部门的最高薪者。关键在于让子查询“感知”当前行所属部门,否则结果完全失真。
正确做法是把子查询写成相关子查询(correlated subquery),在子查询 WHERE 条件里引用外层查询的部门字段:
SELECT * FROM employees e1 WHERE salary = ( SELECT MAX(salary) FROM employees e2 WHERE e2.department_id = e1.department_id );
- 外层别名
e1和内层别名e2必须不同,否则 MySQL 会报Unknown column 'e1.department_id' in 'where clause' - 如果
department_id允许为 NULL,这条语句默认不返回 NULL 部门的记录——因为e2.department_id = e1.department_id对 NULL 比较结果为 UNKNOWN,不满足 WHERE 条件 - 多个员工并列最高薪时,该写法会全部返回,符合“详情”需求;若只要一条,需配合
LIMIT 1或窗口函数
用窗口函数替代子查询更安全且易扩展
相关子查询在大数据量下性能差(每行都执行一次子查询),且语法容易出错。MySQL 8.0+、PostgreSQL、SQL Server 都支持 ROW_NUMBER() 或 RANK(),更直观可靠。
要保留并列情况(即同部门多个最高薪都显示),用 RANK():
SELECT employee_id, name, department_id, salary
FROM (
SELECT *,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rk
FROM employees
) ranked
WHERE rk = 1;
-
PARTITION BY department_id是分组核心,漏写就变成全表排名 -
ORDER BY salary DESC必须明确降序,升序会导致rk = 1取最低薪 - 如果只要每个部门一条记录(哪怕并列也只取其一),改用
ROW_NUMBER(),但结果不稳定(无ORDER BY子句时顺序未定义)
WHERE + IN 子查询在某些场景下会漏数据
有人尝试先查出每个部门的最高薪资,再用 IN 匹配:
SELECT * FROM employees WHERE (department_id, salary) IN ( SELECT department_id, MAX(salary) FROM employees GROUP BY department_id );
这看似简洁,但存在两个硬伤:
- MySQL 5.7 及更早版本不支持行构造符(
(a,b) IN (...)),会直接报错Operand should contain 1 column(s) - 即使支持,当某部门有多个同为最高薪的员工时,该写法仍能正确返回全部;但若
salary字段为浮点类型(如DECIMAL(10,2)),因精度或隐式转换可能导致匹配失败,实际漏行 - GROUP BY 后没加
HAVING COUNT(*) > 0等过滤,空部门不会影响结果,但逻辑上不如窗口函数清晰
NULL 值和空部门需要单独判断
部门字段为 NULL 的员工,无论用相关子查询还是窗口函数,默认都不会被归入任何部门组——也就是说,他们永远不会出现在结果中。这不是 bug,而是 SQL 标准行为:NULL 不等于 NULL。
如果业务要求包含“部门未分配”的最高薪者,必须显式处理:
SELECT employee_id, name, department_id, salary
FROM (
SELECT *,
RANK() OVER (
PARTITION BY COALESCE(department_id, -999)
ORDER BY salary DESC
) AS rk
FROM employees
) ranked
WHERE rk = 1;
-
COALESCE(department_id, -999)把 NULL 统一映射为一个占位值,使其可参与分组 - 不能用
IFNULL(department_id, 'unknown')直接拼字符串,除非department_id是字符串类型,否则类型不匹配会报错 - 这种处理方式会让“无部门”成为一个逻辑上的独立分组,但要注意 -999 是否真实存在于
department_id中
窗口函数写法看着多几行,但逻辑清晰、性能可控、扩展性强;相关子查询虽兼容老版本,但一不留神就掉进 NULL 或别名陷阱里。真正上线前,务必用含 NULL 和并列数据的真实样本跑一遍。











