子查询不能放在where中筛选分组结果,必须用having;因为where在分组前执行,无法使用avg等聚合函数,而having作用于group by之后,支持聚合计算与子查询比较,如having avg(salary) > (select avg(salary) from employees)。

子查询放在 WHERE 里筛分组结果?不行,得用 HAVING
直接在 WHERE 后跟子查询去过滤“每个部门平均工资 > 5000”的结果,会报错或逻辑错误——WHERE 执行时还没分组,根本没 AVG(salary) 这个值。真正该用的是 HAVING,它作用于 GROUP BY 之后的分组行。
-
HAVING可用聚合函数(AVG、COUNT、MAX等),WHERE不行 - 如果二次筛选条件还依赖其他表(比如“部门经理薪资高于公司平均”),才需要子查询嵌套在
HAVING或SELECT中 - MySQL 8.0+ 和 PostgreSQL 支持
HAVING中直接写子查询;SQLite 和旧版 MySQL 要求子查询必须加别名或提前算好
用子查询预计算全局基准,再和分组结果比对
典型场景:找出“平均工资高于公司整体平均工资”的部门。公司平均工资是标量值,得先算出来,再拿去和各分组比。
SELECT dept, AVG(salary) AS avg_dept_salary FROM employees GROUP BY dept HAVING AVG(salary) > (SELECT AVG(salary) FROM employees);
- 括号里的
(SELECT AVG(salary) FROM employees)是标量子查询,只返回一个数,可直接用于比较 - 如果子查询可能返回空(比如员工表为空),
> NULL结果为UNKNOWN,该分组会被过滤掉——注意业务上是否要处理 NULL 场景 - 某些数据库(如 SQL Server)要求子查询不能有歧义,若外层和子查询都引用同一张表,需加别名避免解析冲突
分组后关联其他表做二次筛选,用 JOIN + 子查询或 LATERAL(PostgreSQL)
当二次筛选依赖另一张表的聚合结果(比如“部门中最高薪员工职级 = 高级工程师”),单纯 HAVING 不够,得把分组结果当临时表再 JOIN。
SELECT d.dept, d.avg_salary FROM ( SELECT dept, AVG(salary) AS avg_salary FROM employees GROUP BY dept ) d JOIN ( SELECT dept, MAX(level) AS max_level FROM employees GROUP BY dept ) m ON d.dept = m.dept WHERE m.max_level = 'Senior';
- 两个子查询分别按
dept分组,再通过JOIN关联——本质是把分组结果物化成中间结果集 - PostgreSQL 可用
LATERAL实现更紧凑写法,但 MySQL 不支持;Oracle 用WITH子句更清晰 - 注意 JOIN 条件必须明确,否则产生笛卡尔积;如果某部门在其中一个子查询里不存在(比如没数据),
INNER JOIN会让整行消失
性能坑:子查询被重复执行,记得加索引或改用 WITH
像 HAVING AVG(salary) > (SELECT AVG(salary) FROM employees) 这种写法,部分数据库(如早期 MySQL)会在每个分组行上重跑一次子查询,而不是只算一次。
- 用
WITH先定义公共表达式(CTE),能确保子查询只执行一次:WITH global_avg AS (SELECT AVG(salary) AS g_avg FROM employees) SELECT dept, AVG(salary) FROM employees, global_avg GROUP BY dept HAVING AVG(salary) > global_avg.g_avg;
-
employees.salary字段最好有索引,尤其当表很大时,AVG()全表扫描代价高 - 如果子查询涉及多表 JOIN 或复杂条件,先单独执行看看执行计划(
EXPLAIN),确认是否走了索引
分组后的二次筛选,核心就两点:分清 WHERE 和 HAVING 的执行时机,以及识别出哪些条件必须靠子查询提供标量或关联结果——其余都是围绕这两点做性能和兼容性兜底。










