WHERE在分组前过滤原始行,不可用聚合函数;HAVING在GROUP BY和聚合计算后过滤分组结果,必须配合GROUP BY,可使用COUNT()、SUM()等聚合函数及分组字段。

WHERE 和 HAVING 的执行时机完全不同
SQL 执行顺序是 WHERE → GROUP BY → HAVING。这意味着 WHERE 在数据刚从磁盘读入内存、还没分组时就过滤原始行;而 HAVING 要等所有行完成分组、聚合函数(如 COUNT(*)、AVG())全部算完才开始筛选。
所以你不能在 WHERE 里写 COUNT(*) > 5——它根本还没被计算出来,MySQL 直接报错 Invalid use of group function;反过来,把本该在 WHERE 里做的简单字段过滤(比如 status = 'paid')挪到 HAVING,等于强迫数据库先加载全表、分组、聚合,再扔掉大部分结果——纯属浪费 CPU 和内存。
HAVING 无法引用非分组/非聚合字段
一旦执行完 GROUP BY,原始行级字段(如 salary、created_at)就“消失”了,只留下分组键和聚合值。这时你在 HAVING 里写 salary > 30000,MySQL 会报 Unknown column 'salary' in 'having clause'。
-
HAVING只能用:出现在GROUP BY中的列(如department)、聚合函数(如SUM(amount))、或SELECT中定义的别名(仅部分数据库支持,MySQL 5.7+ 默认不认) -
WHERE则完全不受限,可自由使用任意原始字段和索引
性能差异不是理论,而是真实开销
有索引的字段(比如 user_id、created_at)放在 WHERE 里,MySQL 能在存储引擎层就跳过大量数据块;而放在 HAVING 里,意味着这些数据全得加载进内存、参与分组、触发临时表甚至磁盘排序——IO 和 CPU 成倍上涨。
典型反模式:
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING department = 'tech'; -- ❌ 错!department 是分组字段,但这里本该用 WHERE
应改成:
SELECT department, COUNT(*) FROM employees WHERE department = 'tech' -- ✅ 先筛,再分组,快得多 GROUP BY department;
别名在 HAVING 中是否可用?取决于数据库版本和 SQL 模式
MySQL 默认严格模式下,HAVING 不认 SELECT 中的别名。写 SELECT AVG(score) AS avg_score FROM exams GROUP BY class HAVING avg_score > 85 会失败,必须写成 HAVING AVG(score) > 85。
PostgreSQL 允许,但跨库迁移时极易出错。更麻烦的是:哪怕别名能用,它也只在 HAVING 和 ORDER BY 安全,在 WHERE 和 GROUP BY 里一律无效——这个边界稍不注意就踩坑。
真正容易被忽略的一点:当你要同时依赖原始字段条件和聚合结果时,WHERE + HAVING 必须共存,缺一不可。只靠 HAVING 看似“能跑”,实则掩盖了逻辑错误和性能黑洞。


















