SQL查询每个部门工资最高的员工(详解4种写法与并列情况处理)

查询”每个部门工资最高的员工”,本质是先按部门分组求出最高薪资,再回表匹配对应员工。主流实现有四类:GROUP BY+MAX 派生表关联、关联子查询、多列 IN 子查询、以及窗口函数(ROW_NUMBER/RANK/DENSE_RANK)。最稳妥且跨版本通用的是”先聚合再 JOIN”;若数据库支持窗口函数(MySQL 8.0+、SQL Server 2005+、PostgreSQL 等均支持),用 RANK()/DENSE_RANK() 一次扫描即可,代码更简洁。需要特别留意并列情况——同部门多人同薪时,ROW_NUMBER() 只保留一人,而 RANK()/DENSE_RANK() 会把并列者全部返回。

一、为什么不能直接 GROUP BY 取员工

一个常见误区是直接写 SELECT id, name, salary, dept FROM employees GROUP BY dept。据火山引擎的技术解析,这种写法会触发 MySQL 的 ERROR 1055(违反 only_full_group_by):当按 dept 分组后,MySQL 不知道该返回同部门多个员工中的哪一个 id、name 或 salary,在严格模式下直接报错。正确做法是先算出”各部门最高薪”这个聚合值,再关联回原表取完整行。

二、方法一:GROUP BY + MAX 派生表关联(最通用)

先用子查询按部门求最高薪,再与原表 JOIN 匹配。该写法不依赖窗口函数,兼容所有 MySQL 版本:

sql

SELECT e.id, e.emp_name, e.salary, e.dept
FROM employees e
INNER JOIN (
    SELECT dept, MAX(salary) AS max_salary
    FROM employees
    GROUP BY dept
) dept_max
  ON e.dept = dept_max.dept
 AND e.salary = dept_max.max_salary;

CSDN 的 LeetCode 184 题解与火山引擎均把此作为”兼容所有版本”的推荐写法。派生表 dept_max 只存”部门→最高薪”映射,体积小、易建索引,连接高效。

三、方法二:关联子查询

把外层每行的部门传进子查询,求其部门最高薪再比对:

sql

SELECT department, employee_name, salary
FROM employees e1
WHERE salary = (
    SELECT MAX(salary)
    FROM employees e2
    WHERE e2.department = e1.department
);

Dev.to 与 CloudInsight 都列出此法。它的语义直观,但 CloudInsight 指出:关联子查询对外层每一行都可能重跑一次内部聚合,大表上开销高于窗口函数,性能介于”聚合 JOIN”与”窗口函数”之间。

四、方法三:多列 IN 子查询

将”部门+最高薪”作为复合条件一次性匹配,写法紧凑:

sql

SELECT DeptID, EmpName, Salary
FROM EmpDetails
WHERE (DeptID, Salary) IN (
    SELECT DeptID, MAX(Salary)
    FROM EmpDetails
    GROUP BY DeptID
);

DevGex 的解析说明,子查询先算出每部门的最高薪组合,主查询用 IN 同时匹配部门与薪资,直接返回所有达标的员工完整信息。注意该”行值构造器”语法在部分旧引擎上支持有限,使用前需确认方言兼容性。

五、方法四:窗口函数(推荐,处理并列)

现代数据库支持窗口函数后,这是最干净的方式。用 RANK() 或 DENSE_RANK() 在部门内按薪资降序排名,再取第 1 名:

sql

WITH ranked AS (
    SELECT department, employee_name, salary,
           RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
    FROM employees
)
SELECT department, employee_name, salary
FROM ranked
WHERE rn = 1;

ROW_NUMBER、RANK、DENSE_RANK 的区别

三者都做分区内排序,但对并列的处理不同,直接决定结果是否正确:

  • ROW_NUMBER():无论薪资是否相同,都给出唯一序号。同部门两人同薪时,只保留其中一人,可能漏掉并列的最高薪员工。
  • RANK():相同薪资并列同名次,下一个名次”跳号”(1,1,3…)。取 rn=1 会返回所有并列最高薪者,是本题的正确选择。
  • DENSE_RANK():并列同名次且不跳号(1,1,2…)。取 rn=1 同样返回所有并列最高薪者,区别只在后续名次编号。

Dev.to 明确建议:需处理并列时用 DENSE_RANK() 或 RANK();只取一个代表时用 ROW_NUMBER()。CloudInsight 将 2026 年的最佳实践总结为”优先 CTE + 窗口函数”,窗口函数通常只需一次表扫描。

六、四种方法对比与选型

方法 兼容性 并列处理 扫描次数 推荐度
GROUP BY+MAX 派生表 JOIN 全版本 返回全部并列 1~2 次 通用首选
关联子查询 全版本 返回全部并列 可能逐行 语义清晰时
多列 IN 子查询 多数现代引擎 返回全部并列 1~2 次 写法紧凑
窗口函数 RANK/DENSE_RANK MySQL 8.0+/SQL Server 2005+ 等 返回全部并列 1 次 首选(支持时)

若数据库支持窗口函数,CTE + RANK() 在可读性与性能上最均衡;需兼顾老旧环境时,派生表 JOIN 是稳妥替代。

七、并列情况与避坑清单

  • 并列最高薪:务必用 RANK()/DENSE_RANK() 而非 ROW_NUMBER(),否则会丢失同薪同事。
  • ERROR 1055:避免在非聚合列上直接 GROUP BY 取明细,先聚合再关联。
  • 只取一个代表:业务若规定”同薪取工号最小者”,用 ROW_NUMBER() OVER (... ORDER BY salary DESC, id ASC) 既处理排序又确定唯一性。
  • 关联部门名称:题目常需带出部门名,把员工结果与 Department 表再做一次 JOIN 即可(见 CSDN 题解完整写法)。

常见问题(FAQ)

同部门两人同薪会漏人吗?
用 ROW_NUMBER() 会漏,用 RANK()/DENSE_RANK() 取第 1 名则全部返回。

哪种写法性能最好?
支持窗口函数时,CTE+RANK 通常只扫描一次,优于逐行执行的关联子查询。

MySQL 5.7 能用窗口函数吗?
不能,窗口函数需 MySQL 8.0+;5.7 请用 GROUP BY+MAX 派生表 JOIN 或关联子查询。


本文更新于 2026 年 8 月,内容综合 Dev.to、火山引擎、DevGex、CloudInsight 等公开技术资料整理。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
赵其鑫的头像赵其鑫管理团队

相关推荐

返回顶部