WHERE在GROUP BY前执行,过滤原始行;HAVING在GROUP BY后执行,过滤已聚合的组。WHERE不可用聚合函数,HAVING必须依赖聚合或分组列,二者语义与执行阶段严格绑定。

WHERE在GROUP BY之前执行,HAVING在之后
SQL的执行顺序是硬性规则:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。这不是“建议顺序”,而是数据库引擎实际处理数据的流水线。这意味着WHERE看到的是原始表里一条条未聚合的行;而HAVING看到的是已经按GROUP BY分好、所有COUNT(*)、AVG(salary)等都算完的结果。
WHERE不能用聚合函数,HAVING必须依赖聚合或分组列
常见报错:WHERE COUNT(*) > 5 直接失败——因为此时连分组都没开始,COUNT(*)根本没意义;反过来,HAVING order_date > '2025-01-01' 多数数据库也会报错或返回空,除非order_date出现在GROUP BY列表中(比如GROUP BY order_date),否则它不属于“组”的属性,HAVING无权引用。
-
WHERE可直接写status = 'active',但不能写AVG(score) > 80 -
HAVING可写AVG(score) > 80或COUNT(*) > 10,但不能引用未分组也未聚合的单行字段(如user_name,除非它在GROUP BY里) -
SELECT中定义的别名(如COUNT(*) AS cnt)在HAVING中可用,在WHERE中不可用——不是语法糖,是执行阶段决定的
WHERE过滤行,HAVING过滤组
这是最易被忽略的语义本质。WHERE筛掉的行,彻底不参与后续任何计算;HAVING筛掉的,是整个分组——哪怕该组有200条记录,只要不满足条件,整组消失。
- 写
WHERE created_date >= '2025-01-01':只让2025年及之后的数据进分组,大幅减少中间结果集 - 写
HAVING created_date >= '2025-01-01':先对全部年份分组求和,再试图过滤——多数情况报错,即使不报错也浪费资源 - 性能上,把本该放
WHERE的条件挪到HAVING,等于让数据库多做一遍全量聚合,是典型反模式
HAVING必须配合GROUP BY,WHERE则完全独立
HAVING几乎从不单独出现。没有GROUP BY时硬写HAVING,某些数据库(如MySQL)会把整张表当做一个分组来处理,但这既非标准行为,也不可移植。而WHERE不仅用于SELECT,还能在UPDATE、DELETE中直接使用,且不依赖任何其他子句。
-
DELETE FROM logs WHERE status = 'expired'✅ 合法 -
DELETE FROM logs HAVING COUNT(*) > 100❌ 语法错误,HAVING不能用于DELETE - 没有
GROUP BY却写HAVING,相当于要求数据库“对零个分组做筛选”,逻辑断裂
HAVING SUM(amount) > 100000,却忘了先用WHERE year = 2025把无关年份砍掉——后者才是省资源的关键动作。

















