用ROW_NUMBER()取各部门工资最高员工最可靠,可获取完整信息;GROUP BY+MAX仅得数值。需注意NULL处理、版本兼容及并列情况,避免语法错误与不可控结果。

用窗口函数 ROW_NUMBER() 排序取 Top 1 最可靠
直接用 GROUP BY dept + MAX(salary) 只能拿到最高工资数值,拿不到对应员工姓名、ID 等完整信息——这是初学者最常踩的坑。
正确做法是用窗口函数给每个部门内员工按工资倒序编号,再筛选编号为 1 的记录:
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn = 1;-
ROW_NUMBER()保证每行唯一编号,即使同部门有多个相同最高工资,也能返回其中一行(稳定但不保证具体哪行) - 如果想返回所有并列最高者,换成
RANK()或DENSE_RANK(),然后WHERE rank = 1 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也支持,旧版不支持窗口函数
MySQL 5.7 或更老版本只能用关联子查询
没有窗口函数时,得靠“查出每个部门最高工资 → 再匹配员工”两步走,但要注意 IN 对 NULL 敏感,且性能较差:
SELECT e1.dept, e1.name, e1.salary FROM employees e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM employees e2 WHERE e2.dept = e1.dept );
- 必须确保
dept字段非空,否则e2.dept = e1.dept在任一端为NULL时结果为UNKNOWN,该行被过滤掉 - 如果存在多个同薪最高者,这个写法会全部返回(行为上反而比
ROW_NUMBER()更接近“取全部最高”) - 数据量大时,子查询可能被重复执行,建议在
(dept, salary)上建联合索引
别用 GROUP BY 加非聚合字段——语法错误或结果不可靠
类似下面这种写法在 MySQL 5.7 严格模式下直接报错,在旧版 MySQL 或其他数据库中即使跑通,name 值也是随机的:
SELECT dept, name, MAX(salary) -- ❌ 错误!name 不在 GROUP BY 里,也不在聚合函数中 FROM employees GROUP BY dept;
- PostgreSQL、SQL Server、Oracle 默认拒绝执行,报
column "name" must appear in the GROUP BY clause - MySQL 5.7 开启
ONLY_FULL_GROUP_BY后同样报错;关闭后虽能运行,但name是该分组任意一行的值,完全不可控 - 这不是“取最高工资员工”,只是碰巧显示了某个人名,不能用于生产逻辑
注意 NULL 工资值和空部门的处理
如果 salary 允许为 NULL,ORDER BY salary DESC 会让 NULL 排在最前(多数数据库默认),导致 ROW_NUMBER() = 1 可能命中 NULL 行。
- 加
WHERE salary IS NOT NULL过滤,或显式控制NULLS LAST(PostgreSQL/Oracle 支持,MySQL 不支持) - 如果某部门全员
salary IS NULL,则该部门无结果返回;如需保留空部门占位,得改用LEFT JOIN或 CTE 预生成部门列表 - 部门字段为
NULL的员工会被归入同一组(PARTITION BY dept中NULL = NULL),若业务要求忽略或单独处理,需提前COALESCE(dept, 'unknown')
真正麻烦的不是语法,而是你是否考虑过工资为 NULL、部门为空、多人并列、跨版本兼容这些边界情况——它们往往在线上才暴露。

















