ROW_NUMBER()是最稳当方案,因它通过PARTITION BY分组、ORDER BY排序后编号,再筛选rn=1可精确获取每组首行完整记录;而MAX()+GROUP BY仅返回最大值,丢失整行字段。

用窗口函数 ROW_NUMBER() 最稳当
直接用 GROUP BY + MAX(salary) 查不到完整员工信息,因为聚合会丢行。窗口函数是标准解法,兼容 PostgreSQL、SQL Server、MySQL 8.0+ 和 Oracle。
关键不是“找最高工资”,而是“给每个部门的员工按工资倒序编号”,再筛出编号为 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() -
PARTITION BY dept是分组依据,不是GROUP BY,不压缩行数 - ORDER BY salary DESC 必须写全,漏掉
DESC就会选最低的
MySQL 5.7 或 SQLite 这类不支持窗口函数怎么办?
得靠关联子查询或自连接,性能差但能跑通。核心思路:对每个员工,查他所在部门是否存在工资更高的员工;不存在,他就上榜。
SELECT e1.dept, e1.name, e1.salary FROM employees e1 WHERE NOT EXISTS ( SELECT 1 FROM employees e2 WHERE e2.dept = e1.dept AND e2.salary > e1.salary );
- 注意是
e2.salary > e1.salary,不是>=,否则同薪时全被排除 - 如果部门有 NULL 值,
e2.dept = e1.dept会失效,需额外加IS NOT NULL判断 - 数据量稍大(比如部门超千人)时,没索引会明显变慢;建议在
(dept, salary)上建联合索引
GROUP BY 配合 JOIN 容易踩哪些坑?
有人先用 GROUP BY dept 拿最大工资,再连原表匹配——这方法看似直观,但 MySQL 5.7 默认开启 ONLY_FULL_GROUP_BY,会报错;即使关了,也可能返回非预期员工。
- 错误写法:
SELECT dept, name, MAX(salary) FROM employees GROUP BY dept——name不在GROUP BY里,MySQL 可能随便挑一个,不是最高薪那个 - 正确补救:先聚合出
(dept, max_salary),再和原表JOIN,且必须同时匹配dept和salary两列 - 如果同一部门多个员工并列最高薪,这种写法能返回全部;但比窗口函数多一次扫描,逻辑也更绕
NULL 工资或空部门要不要特殊处理?
现实数据常有脏值。salary IS NULL 的记录默认排在 ORDER BY ... DESC 最末尾,不会被 ROW_NUMBER() = 1 选中——这通常符合预期。但部门字段为空时,PARTITION BY dept 会让所有 NULL 归成一组,可能意外凑出一个“空部门最高薪”。
- 如果业务上不允许空部门,查前先过滤:
WHERE dept IS NOT NULL - 如果允许,但不想让它参与排名,可在窗口函数里加条件:
PARTITION BY CASE WHEN dept IS NULL THEN '' ELSE dept END - 别依赖数据库默认排序行为——有些版本对 NULL 的处理不一致,显式写
ORDER BY salary DESC NULLS LAST更稳妥(PostgreSQL 支持,MySQL 不支持该语法)
PARTITION BY 和 ORDER BY 的组合容易写反顺序,一错就全错;手写子查询虽然啰嗦,反而更容易逐层验证。实际选哪种,得看你的数据库版本和数据质量。

















