不能直接用 group by + max() 获取完整员工信息,因为 max() 只返回最大值而不保留对应行的其他字段,name 会随机取组内某行值;正确方法包括相关子查询、窗口函数(如 rank())或 group by 联合 join。

为什么不能直接用 GROUP BY + MAX() 拿到完整员工信息
很多人写 SELECT dept, MAX(salary), name FROM emp GROUP BY dept,结果发现 name 字段值是错的——MySQL 5.7+ 默认拒绝这种非函数非分组字段的写法,而旧版本或某些配置下虽能执行,但 name 实际返回的是该组中「任意一行」的值,不保证对应最高工资那条记录。核心问题在于:GROUP BY 聚合后丢失了行级关联,MAX() 只给数值,不带回原始记录。
用相关子查询匹配每组最高工资的完整记录
思路是:对每条员工记录,查出其所在部门的最高工资,再判断当前员工工资是否等于这个最大值。这样保留了原始行的所有字段。
典型写法:
SELECT e1.name, e1.dept, e1.salary FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE e2.dept = e1.dept );
注意点:
- 子查询必须用
e2.dept = e1.dept关联外层,否则变成全表最高工资,所有部门都只返回一个结果 - 如果某部门有多个员工并列最高工资,这条语句会全部返回——这是合理行为,不是 bug
- 性能上,每行都会触发一次子查询,数据量大时建议在
(dept, salary)上建联合索引
用窗口函数替代子查询(推荐用于 MySQL 8.0+/PostgreSQL/SQL Server)
如果数据库支持窗口函数,ROW_NUMBER() 或 RANK() 更直观高效:
SELECT name, dept, salary
FROM (
SELECT name, dept, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM emp
) t
WHERE rk = 1;
区别说明:
-
RANK()并列时跳过后续名次(比如两个第1名,则下一个为第3名),适合“所有最高者”场景 -
ROW_NUMBER()强制唯一编号,会随机排序并列项,可能漏掉真正并列的高薪员工 - 不需要关联子查询,执行计划更清晰,大数据量下通常更快
GROUP BY 配合 JOIN 的写法(兼容老版本但稍冗长)
先聚合出每个部门的最高工资,再和原表连接匹配:
SELECT e.name, e.dept, e.salary FROM emp e INNER JOIN ( SELECT dept, MAX(salary) AS max_sal FROM emp GROUP BY dept ) m ON e.dept = m.dept AND e.salary = m.max_sal;
这个写法逻辑清晰,但要注意:
- 必须同时匹配
dept和salary,缺一不可,否则会笛卡尔积 - 如果存在相同
(dept, salary)多条记录(比如同部门同薪资不同人),会全部返回,符合预期 - 比相关子查询更容易被优化器利用索引,尤其当
dept区分度高时
实际选哪种,取决于你用的数据库版本、是否接受并列结果、以及表数据量。子查询写法最通用,但别忘了加关联条件;窗口函数最干净,但得确认环境支持;JOIN 方式折中,可读性好且性能可控。










