子查询必须关联外层表才能准确获取每个部门最高薪员工;正确写法需在子查询中添加where e2.dept_id = e1.dept_id条件,并注意null处理、索引优化及窗口函数替代方案。

子查询必须关联外层表才能避免全表误匹配
直接用 SELECT * FROM emp WHERE salary = (SELECT MAX(salary) FROM emp) 会返回所有部门中薪水等于全局最高值的员工,不是每个部门各自的最高薪者。关键在于子查询要“感知”当前外层行所属的部门。
正确写法是让子查询带上 WHERE dept_id = outer.dept_id 关联条件:
SELECT e1.name, e1.dept_id, e1.salary FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE e2.dept_id = e1.dept_id );
- 别名
e1和e2必须不同,否则 SQL 引擎无法区分内外层引用 - 子查询里不能引用外层未出现在
SELECT或WHERE中的字段(比如e1.hire_date),否则报错或结果异常 - 若某部门有多个员工并列最高薪,该语句会全部返回——这是预期行为,不是 bug
遇到 NULL 部门或空部门时结果可能意外缺失
如果 dept_id 允许为 NULL,上述子查询在 e1.dept_id IS NULL 时,e2.dept_id = e1.dept_id 比较结果为 UNKNOWN(三值逻辑),导致子查询不返回任何值,外层行被过滤掉。
需要显式处理 NULL 分组:
SELECT e1.name, e1.dept_id, e1.salary FROM emp e1 WHERE e1.salary = ( SELECT MAX(e2.salary) FROM emp e2 WHERE (e2.dept_id = e1.dept_id) OR (e2.dept_id IS NULL AND e1.dept_id IS NULL) );
- 更稳妥的做法是先排除
NULL:加AND e1.dept_id IS NOT NULL到外层WHERE - 空部门(无员工)自然不会出现在结果中——子查询返回空集,
=判定为 false,这点无需额外干预
性能差?检查是否对 dept_id + salary 建了联合索引
上面的子查询对每个外层行都执行一次内层扫描,数据量大时很慢。优化核心是让数据库能快速定位“某部门下最高薪水是多少”,而不是遍历整个 emp 表。
推荐索引:
CREATE INDEX idx_dept_salary ON emp(dept_id, salary DESC);
-
dept_id必须放前导列,否则索引无法用于WHERE e2.dept_id = ?过滤 -
salary DESC可让MAX()直接取索引第一条,避免排序 - 没有这个索引时,10 万行数据可能从秒级变数分钟;建完通常降到毫秒级
MySQL 8.0+ 或 PostgreSQL 更推荐用窗口函数替代
子查询写法易懂但难扩展(比如要同时取前三高薪)。窗口函数更直观、性能更稳:
SELECT name, dept_id, salary
FROM (
SELECT name, dept_id, salary,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk
FROM emp
) ranked
WHERE rk = 1;
-
RANK()保留并列名次(两个第一则下一个为第三),ROW_NUMBER()则强制连续编号 - 注意:SQLite 和旧版 MySQL 不支持窗口函数,此时子查询仍是唯一可靠选择
- 即使支持窗口函数,也建议在
PARTITION BY dept_id ORDER BY salary DESC字段上保持相同联合索引
子查询本身没问题,但它的脆弱性藏在关联逻辑和索引依赖里——漏掉任一环,查出来的就不是你要的“每个部门最高薪员工”。











