正确写法是使用相关子查询或窗口函数:相关子查询需通过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) 会返回所有部门里最高薪资的任意一条记录(甚至可能跨部门),根本不是“每个部门”的结果。关键在于让子查询感知当前行所属部门,否则就变成全局比较了。
正确做法是用相关子查询,在子查询中引用外部查询的 department_id:
SELECT * FROM employees e1 WHERE salary = ( SELECT MAX(salary) FROM employees e2 WHERE e2.department_id = e1.department_id );
- 别名
e1和e2必须区分,否则 SQL 解析器无法识别作用域 - 子查询里的
WHERE e2.department_id = e1.department_id是关联点,缺了它就失去“按部门”语义 - 如果一个部门有多人并列最高薪,这条语句会全部返回——这是符合需求的,不是 bug
用窗口函数替代子查询更清晰高效
嵌套子查询在数据量大时性能容易变差,因为对每一行都要执行一次内层聚合。MySQL 8.0+、PostgreSQL、SQL Server 都支持 ROW_NUMBER() 或 RANK(),逻辑更直白:
SELECT employee_id, name, department_id, salary
FROM (
SELECT *,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM employees
) ranked
WHERE rnk = 1;-
PARTITION BY department_id确保分组计算,不是全表排一列 - 用
RANK()而不是ROW_NUMBER(),才能保留并列最高薪(ROW_NUMBER()会强行编号为 1,2,3…) - 注意:SQLite 默认不支持窗口函数;老版本 MySQL(
WHERE 子句中不能直接用聚合函数,但可以嵌套子查询
常见错误是写 SELECT * FROM employees WHERE MAX(salary) ...,这会报错 Invalid use of group function。SQL 规范要求聚合函数只能出现在 SELECT 列表或 HAVING 子句中,不能直接用于 WHERE。
所以必须把聚合结果“提前算好”,再拿去比对——也就是靠子查询把 MAX(salary) 变成一个标量值或结果集:
- 单值比较(如找某部门最高薪):子查询返回一行一列,可用
= - 多值匹配(如找所有部门最高薪):子查询返回一列多行,要用
IN,但要注意NULL值会让整个IN判断为UNKNOWN,导致漏数据 - 更稳妥的写法是显式排除
NULL:WHERE salary IN (SELECT MAX(...) FROM ... WHERE department_id IS NOT NULL)
GROUP BY 和子查询结果字段要严格对齐
有人尝试先用 GROUP BY department_id 拿到每个部门的 MAX(salary),再连回原表。这时候如果 SELECT 里写了 name 或 employee_id 却没加进 GROUP BY,MySQL 5.7+ 严格模式下会报错 Expression #1 of SELECT list is not in GROUP BY clause。
解决方法只有两个:要么所有非聚合字段都进 GROUP BY(但会破坏分组语义),要么放弃 GROUP BY 方案,回归相关子查询或窗口函数。
真正需要“详情”时,GROUP BY 本身就不适用——它只适合汇总统计,不是查原始记录。

















