子查询先算各班平均分再筛选,因WHERE不支持聚合函数;HAVING可直接在GROUP BY后过滤,但无法处理空班级或需扩展字段的场景。
核心思路是把“班级平均分”当做一个中间结果,再对这个结果做筛选。不能直接在 WHERE 里写 AVG(score),因为聚合函数不能出现在普通 WHERE 子句中——会报错 ERROR 1111 (HY000): Invalid use of group function。
所以得用子查询先把每个班的平均分算出来,比如:
SELECT class_id, AVG(score) AS avg_scoreFROM studentsGROUP BY class_id
这个结果就是一张临时表,下一步再从中挑出 avg_score >= 60 的记录。
把上面的子查询作为 FROM 子句里的数据源,就能在外层安全地加 WHERE 条件了:
SELECT class_idFROM ( SELECT class_id, AVG(score) AS avg_score FROM students GROUP BY class_id) AS class_avgWHERE avg_score >= 60;
注意几点:
AS class_avg),否则 MySQL 会报错 Every derived table must have its own alias
AVG() 默认忽略 NULL 值,如果某班有大量缺考(score IS NULL),平均分可能偏高,实际可能不具代表性classes 表,不能只靠 class_id —— 子查询里没带 class_name
有人会想:既然要按班级分组再筛平均分,直接用 HAVING 不更简单?确实可以:
SELECT class_idFROM studentsGROUP BY class_idHAVING AVG(score) >= 60;
但要注意,这和嵌套查询不是完全等价的:
HAVING 是对分组后的结果过滤,前提是你要查的是分组字段或聚合值;如果后续还要查该班级的最高分、人数、学生名单等,HAVING 就不够用了class_id 在 students 表里无记录),HAVING 版本根本查不到它;而嵌套查询配合 LEFT JOIN classes 可以体现“空班”,这点容易被忽略HAVING 写法简洁,但可读性弱——别人一眼看不出你是在筛班级,还是在筛学生当数据量大时,嵌套查询可能比 HAVING 多一次临时表物化,但差别通常不大;真正容易出问题的是 NULL 和空班级:
AVG(score) 遇到全为 NULL 的班级,返回 NULL,而 NULL >= 60 结果为 UNKNOWN,该班级不会出现在结果中——这符合直觉,但若业务要求“零分也算不及格”,就得显式写成 COALESCE(AVG(score), 0) >= 60
score = -1 表示未考试),得先在子查询里 WHERE score > 0 过滤,否则平均分会失真WITH class_avg AS (...) SELECT * FROM class_avg WHERE ... 提高可读性,但本质和子查询一样嵌套查询本身不难,难的是想清楚“及格”到底指什么:是平均分 ≥ 60,还是至少一半人及格,还是去掉最高最低后的均值?这些语义差异,都会让 SQL 变得完全不同。