GROUP BY 无法直接选出每个部门工资最高的人,因其仅支持分组聚合(如MAX),不能同时返回非聚合字段;推荐用ROW_NUMBER()窗口函数按部门分组、工资降序编号取rn=1的记录。

GROUP BY 本身不能直接选出“每个部门工资最高的人”
这是个常见误解。GROUP BY 只负责分组和聚合(比如 MAX(salary)),但无法同时返回该最高工资对应的具体员工姓名、ID 等非聚合字段——除非这些字段在语义上完全依赖于分组键(如部门名),否则直接 SELECT dept, MAX(salary), name FROM emp GROUP BY dept 在标准 SQL 中会报错(MySQL 5.7+ 严格模式下同样拒绝)。
用窗口函数 ROW_NUMBER() 是最清晰可靠的解法
适用于 PostgreSQL、SQL Server、Oracle、MySQL 8.0+、SQLite 3.25+。核心思路:按部门分组,对工资降序编号,取每组 rn = 1 的记录。
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM emp
) ranked
WHERE rn = 1;
- 如果同一部门有多个员工并列最高工资,
ROW_NUMBER()只随机选一个;需要全部返回时改用RANK()或DENSE_RANK() -
PARTITION BY dept对应 “每个部门”,ORDER BY salary DESC确保最高工资排第一 - 避免写成
ORDER BY salary DESC, id ASC之类附加排序——除非你明确想打破并列时的不确定性
兼容老版本 MySQL(5.6/5.7)的替代写法:用相关子查询
不依赖窗口函数,但性能通常较差,尤其数据量大时。原理是:对每条员工记录,检查是否存在同部门、工资更高的员工;若不存在,说明当前人就是该部门最高薪者。
SELECT e1.dept, e1.name, e1.salary FROM emp e1 WHERE NOT EXISTS ( SELECT 1 FROM emp e2 WHERE e2.dept = e1.dept AND e2.salary > e1.salary );
- 注意是
e2.salary > e1.salary(严格大于),不是>=,否则会漏掉唯一最高者 - 如果存在 NULL 工资,需额外加
AND e2.salary IS NOT NULL,否则NULL > X结果为 UNKNOWN,逻辑失效 - 务必给
(dept, salary)建联合索引,否则全表扫描代价极高
为什么不要用 GROUP BY + MAX() 再连表?
有人会先查出各部门最大工资:SELECT dept, MAX(salary) AS max_sal FROM emp GROUP BY dept,再用 JOIN 回原表匹配。这看似可行,但隐含严重问题:
- 若同一部门多人同为最高工资,该方法能返回全部——这点比
ROW_NUMBER()更“全”,但代价是两次扫描+连接开销 - 但如果原表中存在重复
(dept, salary)组合但其他字段(如name)不同,而业务上又要求“任意一个最高薪者”,那这种写法反而可能因 JOIN 产生意外笛卡尔积 - 更隐蔽的坑:当
salary字段有浮点数或精度误差时,MAX()和等值 JOIN 可能因舍入不一致而匹配失败
真正需要“所有并列最高者”时,优先用 RANK();只要一个代表,窗口函数加 ROW_NUMBER() 更可控。连表方案只在极简场景且数据绝对干净时才值得考虑。

















