用子查询找每个部门薪水最高的人易漏同薪多人,因MAX只返回数值;正确写法需相关子查询匹配所有并列最高薪者,或用RANK()/DENSE_RANK()窗口函数。

用子查询找每个部门薪水最高的人,为什么容易漏掉同薪多人?
直接 MAX(salary) 只能拿到数值,没法带回对应员工信息。常见错误是写成:SELECT dept, MAX(salary), name FROM emp GROUP BY dept —— 这在大多数数据库(如 PostgreSQL、SQL Server)会报错,因为 name 不在 GROUP BY 里也没被聚合;MySQL 5.7+ 默认也拒绝这种非确定性写法。
正确思路是:先用子查询算出每个部门的最高薪,再和原表关联匹配:
SELECT e1.dept, e1.name, e1.salary FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE e2.dept = e1.dept );
- 这个写法能返回所有并列最高薪者(比如市场部两人都是 18000,都会出来)
- 注意子查询必须相关(
e2.dept = e1.dept),否则变成全表最大值 - 性能上,如果
dept和salary没有联合索引,可能触发全表扫描
窗口函数 ROW_NUMBER() vs RANK() 的关键区别
用 ROW_NUMBER() 会强制给同薪者编号(如 1,2,3),即使薪水相同;而 RANK() 会并列且跳号(如 1,1,3),DENSE_RANK() 并列但不跳号(1,1,2)。要“所有最高薪者”,必须用 RANK() 或 DENSE_RANK():
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM emp
) t
WHERE t.rk = 1;
-
PARTITION BY dept是分组依据,不是GROUP BY,不压缩行数 -
ORDER BY salary DESC决定谁排第一;如果想按姓名补序,可写成ORDER BY salary DESC, name ASC - MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+ 支持;旧版 MySQL(5.7 及以前)不支持窗口函数
当需要“每个部门只取一人”时,ROW_NUMBER() 更可控
业务有时明确要求“同薪取入职最早/姓名字典序最小的那个”,这时 ROW_NUMBER() 配合多字段排序最稳妥:
SELECT dept, name, salary, hire_date
FROM (
SELECT dept, name, salary, hire_date,
ROW_NUMBER() OVER (
PARTITION BY dept
ORDER BY salary DESC, hire_date ASC
) AS rn
FROM emp
) t
WHERE t.rn = 1;
- 排序字段顺序很重要:先按薪水降序,再按入职时间升序,就能优先选高薪中最早的
- 如果两个字段都可能为 NULL,记得加
NULLS FIRST或NULLS LAST(PostgreSQL/Oracle 支持;MySQL 忽略该子句) - 别误用
ORDER BY ... LIMIT 1套子查询——那只能取全局第一条,不是每个部门一条
真实场景中容易被忽略的 NULL 和空字符串陷阱
部门字段为 NULL 时,PARTITION BY dept 会把所有 NULL 归成一组;而 WHERE dept = ... 子查询则完全跳过 NULL 行。两者行为不一致。
- 检查数据:用
SELECT COUNT(*) FROM emp WHERE dept IS NULL确认是否有脏数据 - 若需排除 NULL 部门,窗口函数方案加
WHERE dept IS NOT NULL;子查询方案要在内外层都加 - 某些系统里部门名含空格或全角字符,
=比较可能因 collation 失效,建议先TRIM()或用LIKE辅助验证
真正麻烦的不是语法,而是搞清“最高薪”在业务里究竟指唯一人选,还是允许并列;以及 NULL 值到底代表“未知部门”还是“未分配”,这直接影响该不该参与统计。

















