不能直接用 GROUP BY 配合 MAX() 做行级过滤,因为 MAX() 仅返回聚合值,不保留对应行的其他字段(如 name),且非分组非聚合字段在严格模式下会报错;正确方法是用相关子查询或窗口函数。

为什么不能直接用 GROUP BY 配合 MAX() 做行级过滤
想查“每个部门工资最高的人”,很多人第一反应是 SELECT dept, MAX(salary), name FROM emp GROUP BY dept —— 这会报错或返回错误的 name(MySQL 5.7+ 默认拒绝,PostgreSQL/SQL Server 直接报错)。因为 MAX(salary) 是聚合结果,而 name 不在 GROUP BY 中也没被聚合,数据库无法确定该取哪一行的 name。
相关子查询怎么写才对:用外层行驱动内层比较
核心思路是:对每一行,检查它的 salary 是否等于“同部门所有人的最高工资”。这时内层查询必须能访问外层的 dept 值,也就是“相关”(correlated)。
正确写法:
SELECT e1.name, e1.dept, e1.salary FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE e2.dept = e1.dept );
-
e1和e2是同一张表的两个别名,避免歧义 - 子查询里
WHERE e2.dept = e1.dept是关键——没有这句就是全表最大值,加了才是“当前行所在部门”的最大值 - 如果一个部门有多个员工并列最高薪,这条语句会全部返回(符合业务预期)
遇到重复最大值、NULL 或性能问题怎么办
相关子查询逻辑清晰,但实际用时容易踩坑:
- 如果
salary列允许NULL,MAX()会忽略它,但外层e1.salary = NULL永远不成立(NULL 不能用=比较),导致 NULL 行永远被过滤。需要额外处理:OR (e1.salary IS NULL AND NOT EXISTS (SELECT 1 FROM emp e2 WHERE e2.dept = e1.dept AND e2.salary IS NOT NULL)) - 数据量大时性能差:每行都触发一次子查询。有索引(如
(dept, salary)联合索引)能缓解,但不如窗口函数高效 - 想只取一条(比如按姓名排序后取第一个),相关子查询本身做不到,得嵌套
ROW_NUMBER()或改用LIMIT 1+ORDER BY(MySQL/PostgreSQL)
什么时候该换窗口函数而不是硬扛相关子查询
如果你用的是 PostgreSQL 8.4+、SQL Server 2005+、MySQL 8.0+ 或 Oracle,优先考虑 ROW_NUMBER() 或 RANK():
SELECT name, dept, salary
FROM (
SELECT name, dept, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM emp
) t
WHERE rk = 1;
-
RANK()保留并列(两个第1名后直接跳到第3名),ROW_NUMBER()强制编号(两个第1名变成1和2) - 执行计划通常比相关子查询友好,尤其数据量过万时
- 但老版本 MySQL(5.7 及更早)不支持,此时相关子查询仍是可靠 fallback
真正难的不是写出语法,而是想清楚:你要的是“所有并列最大值”,还是“任意一个最大值”,或是“按某种顺序排第一的那个”——选错语义,再对的 SQL 也解决不了问题。










