用in配合group by查最高工资员工易出错,因in仅匹配工资值而忽略部门上下文,导致跨部门误选;正确做法是用(dept_id, salary)联合子查询结果匹配。

为什么用 IN 配合 GROUP BY 查最高工资员工容易出错
直接写 SELECT * FROM emp WHERE salary IN (SELECT MAX(salary) FROM emp GROUP BY dept_id) 看似合理,实际会返回所有部门中任意一个最高工资值对应的所有员工——比如部门 A 和 B 的最高工资都是 15000,那所有工资为 15000 的员工(无论属于哪个部门)都会被查出来。根本原因是 IN 只匹配数值,不绑定部门上下文。
正确写法:用 (dept_id, salary) 联合匹配
必须让子查询返回「每个部门 + 对应最高工资」的组合,主查询再按这两个字段同时匹配。这是唯一能避免跨部门误匹配的方式:
SELECT * FROM emp WHERE (dept_id, salary) IN ( SELECT dept_id, MAX(salary) FROM emp GROUP BY dept_id );
注意点:
- MySQL 8.0+、PostgreSQL、SQL Server 2016+ 支持这种行构造器语法;SQLite 也支持;但旧版 MySQL(JOIN 或窗口函数
- 如果某部门有多个员工并列最高工资,这条语句会全部返回——这是符合业务预期的,不是 bug
-
dept_id和salary类型必须严格一致,比如都是INT,不能一边是INT一边是DECIMAL
当数据库不支持行构造器时的替代方案
Oracle 或老版本 MySQL 用户不能用 (a,b) IN (SELECT a,b...),得拆成自连接或相关子查询:
SELECT e1.* FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE e2.dept_id = e1.dept_id );
这个写法的关键在于子查询里加了 WHERE e2.dept_id = e1.dept_id,把外层员工的部门 ID 传进去,实现逐行绑定。性能上要注意:
- 务必在
(dept_id, salary)上建联合索引,否则每次子查询都要全表扫描 - 如果员工表很大且部门数多,相关子查询可能比窗口函数慢,此时建议升级到支持
ROW_NUMBER()的版本 - 某些方言(如早期 SQL Server)对相关子查询的 NULL 处理较敏感,若
salary允许为空,需额外加AND e2.salary IS NOT NULL
为什么不用 ORDER BY ... LIMIT 1 每部门取一条
有人想用 SELECT * FROM emp GROUP BY dept_id ORDER BY salary DESC LIMIT 1,这在语法上就错:MySQL 5.7+ 严格模式下会报错 Expression #1 of SELECT list is not in GROUP BY clause,因为 SELECT * 中其他字段(如 name)没参与分组也没聚合。即使关掉严格模式,结果也是随机的——name 值来自该部门任意一行,不保证和 MAX(salary) 同行。
真正安全的做法始终是:先确定每部门的最高工资(含部门维度),再回表精确匹配。中间少跳一步,就容易漏掉部门上下文。










