必须用相关子查询或窗口函数按部门计算最高薪,直接查全局最大值会忽略部门维度;相关子查询通过e2.department_id = e1.department_id关联,窗口函数用PARTITION BY department_id分组,二者均需单独处理NULL值。

子查询必须关联部门字段,否则会返回全局最高值
直接用 SELECT * FROM employees WHERE salary = (SELECT MAX(salary) FROM employees) 会查出整个公司薪资最高的那条记录,跟部门无关。关键在于让子查询“按部门算最大值”,而不是算一次全局最大值。
正确做法是把外层查询的部门 ID 传进子查询,形成相关子查询:
SELECT e1.* FROM employees e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM employees e2 WHERE e2.department_id = e1.department_id );
- 子查询里的
e2.department_id = e1.department_id是核心,它让每个员工记录都触发一次“本部门最高薪”的计算 - 如果
department_id为NULL,这部分员工会被整体忽略(因等值条件不成立),需单独处理 - 多个员工并列最高薪时,所有并列者都会被查出,这是合理行为,不是 bug
用窗口函数更稳,但需确认数据库版本支持
MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持 ROW_NUMBER() 或 RANK(),比相关子查询性能更好,逻辑也更直观。但老版本 MySQL(5.7 及以前)不支持,强行用会报错 ERROR 1064。
推荐用 RANK() 而非 ROW_NUMBER(),因为要保留并列情况:
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决定谁排第一;若薪资相同,RANK()给相同名次(如 1,1,3),ROW_NUMBER()则强制连续编号(1,2,3) - 注意:如果某部门所有
salary都是NULL,该部门不会返回任何记录(因NULL不参与排序比较)
WHERE 子句里不能直接用聚合结果,会报错 Invalid use of group function
有人会写 SELECT * FROM employees WHERE salary = MAX(salary) GROUP BY department_id,这在任何主流 SQL 引擎中都会失败。聚合函数不能出现在 WHERE 中,因为 WHERE 执行顺序早于分组和聚合。
- 想过滤聚合结果,必须用
HAVING,但它只能配合GROUP BY使用,且只能引用分组字段或聚合表达式 -
HAVING无法返回明细行(它作用于分组后的一行结果),所以不能替代子查询或窗口函数 - 硬要用
GROUP BY拼明细?得靠JOIN回原表,反而更啰嗦且易出错
NULL 值和空部门是隐形陷阱
当 department_id 为 NULL 时,上面所有方案默认跳过——相关子查询中的等值判断失效,窗口函数的 PARTITION BY 会把所有 NULL 归为一组,但多数人其实希望它们被忽略或单独列出。
- 若需包含
NULL部门的最高薪员工,子查询中要把条件改成IS NOT DISTINCT FROM(PostgreSQL)或用OR (e2.department_id IS NULL AND e1.department_id IS NULL)(兼容写法) - 空部门(即没有员工的部门)永远不会有结果,这是预期行为;如需列出这些部门并显示
NULL,就得用departments表左连,再套子查询 - 薪资字段含
NULL时,MAX()会自动忽略,但ORDER BY ... DESC会把NULL排最前(MySQL)或最后(PostgreSQL),行为不统一,建议显式用ORDER BY salary DESC NULLS LAST(如果支持)
实际跑通的关键不在语法多炫,而在想清楚“按部门”这个分组动作到底由谁执行、在哪执行、对 NULL 怎么反应。漏掉任一环,结果就悄悄偏了。

















