where 在 avg() 之前过滤数据,即先筛选原始行再计算平均值;null 自动被跳过,业务干扰值(如 -1、test 账号)须用 where 显式排除;having 仅用于分组后筛选,不可替代 where。

WHERE 在 AVG() 之前就筛数据,不是“先算再过滤”
很多人误以为 AVG() 算完再用 WHERE 去排除结果,其实完全相反:WHERE 是在聚合前就对原始行做过滤。也就是说,AVG(column) 只会基于 WHERE 留下的那些行计算平均值,空值(NULL)自动被跳过,不参与计数也不参与求和。
常见错误是试图在 SELECT 里写 WHERE AVG(score) > 60 —— 这语法直接报错,因为 WHERE 不能引用聚合结果。
-
WHERE作用于每行原始数据,决定哪些行进到AVG() -
NULL值天然不参与AVG(),无需额外WHERE column IS NOT NULL(除非你还想排除 0、负数等业务意义上的“干扰值”) - 若字段本身允许
NULL,但你想把NULL当作 0 处理,得用COALESCE(column, 0),但注意这会拉低平均值
排除业务干扰值:比如剔除测试账号、异常高分或缺考标记
真实场景中,“干扰”往往不是 NULL,而是人为设定的占位值,比如 score = -1 表示缺考、user_type = 'test' 表示测试账号、score > 100 显然录入错误。这些必须靠 WHERE 显式过滤。
例如:
SELECT AVG(score) FROM exam_result WHERE score BETWEEN 0 AND 100 AND user_type != 'test' AND status = 'completed';
- 别只写
score > 0,漏掉score = 0(可能是真实零分);用BETWEEN 0 AND 100更稳妥 - 多个条件用
AND连接,避免逻辑短路导致意外包含 - 如果缺考统一记为
score = -1,一定要加score != -1,否则-1会被当真实分数参与计算
HAVING 不能替代 WHERE 做原始行过滤
HAVING 是对分组后结果的筛选,它发生在 GROUP BY 和聚合之后。如果你没用 GROUP BY,却写了 HAVING,多数数据库(如 MySQL 5.7+、PostgreSQL)会直接报错或警告;即使允许(如某些 MySQL 兼容模式),行为也不可靠。
错误写法:
SELECT AVG(score) FROM exam_result HAVING score > 0; -- ❌ score 在 HAVING 中不可见
正确写法始终是:
SELECT AVG(score) FROM exam_result WHERE score > 0; -- ✅
-
HAVING只能引用聚合函数或GROUP BY字段,不能引用原始列(除非该列在GROUP BY中) - 想按班级看平均分且只保留平均分 ≥ 80 的班级?这时才用
GROUP BY class_id HAVING AVG(score) >= 80
性能提醒:WHERE 条件越早过滤,AVG() 越快
AVG() 本身不慢,但数据量大时,扫描全表再计算很耗时。把过滤条件写进 WHERE 能让数据库尽早丢弃无关行——尤其是当过滤字段上有索引时(如 status、created_at)。
- 避免在
WHERE中对字段做函数操作,比如WHERE YEAR(created_at) = 2023会失效索引;改用WHERE created_at >= '2023-01-01' AND created_at - 如果常按
user_type和status过滤,考虑建联合索引:INDEX(user_type, status) - 确认执行计划(
EXPLAIN)是否真的用了索引,而不是全表扫描
真正容易被忽略的是:业务定义的“干扰值”往往随时间变化,比如今年新增了 score = 999 表示系统异常,明年可能变成 9999。WHERE 条件得跟着业务规则一起维护,不能写死就扔那儿不管。











