SQL 中 WHERE 和 HAVING 有什么区别?
简化版
WHERE 在分组前过滤行,HAVING 在分组后过滤组,因此聚合条件通常写在 HAVING。能在分组前确定的普通条件应尽量写进 WHERE,既准确表达语义,也能减少参与聚合的数据。
详细版
SELECT department_id, COUNT(*) AS employee_count
FROM employee
WHERE status = 'active'
GROUP BY department_id
HAVING COUNT(*) >= 10;
这里 WHERE 先排除非在职员工,GROUP BY 再按部门分组,HAVING 最后只保留在职员工不少于 10 人的部门。
WHERE COUNT(*) >= 10 是错误思路,因为过滤行时聚合结果尚未形成。反过来,把 status = 'active' 只写在 HAVING 中不仅可能不符合严格分组规则,还会让无关行先参与分组。
完整版教学
一、两者过滤的对象不同
WHERE 面对的是 FROM/JOIN 产生的单行数据;HAVING 面对的是 GROUP BY 形成的组。可以把查询理解为先挑原材料,再按规则装箱,最后筛选箱子。
因此以下职责很清楚:
- 时间、状态、地区等行级条件放
WHERE; - 每组数量、总额、平均值等聚合条件放
HAVING; - 同时存在时,两者可以共同出现。
二、为什么聚合函数不能直接放 WHERE
SELECT customer_id, SUM(amount)
FROM orders
WHERE SUM(amount) > 10000
GROUP BY customer_id;
处理 WHERE 时还没有分组,也就没有每个客户的 SUM(amount)。正确写法是:
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 10000;
部分数据库允许 HAVING 使用聚合别名,部分场景需要重复聚合表达式。若追求跨数据库可移植性,应了解目标数据库的别名规则。
三、没有 GROUP BY 能不能使用 HAVING
可以。没有显式 GROUP BY 且查询包含聚合时,所有输入行可视为一个组:
SELECT COUNT(*) AS total
FROM orders
HAVING COUNT(*) > 0;
它要么返回一行,要么不返回。不过普通行级过滤仍应使用 WHERE,不要因为语法允许就混淆职责。
四、性能优化的关键是尽早缩小数据集
SELECT customer_id, COUNT(*)
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY customer_id
HAVING COUNT(*) >= 5;
日期条件在聚合前即可判断,写入 WHERE 后,旧订单无需进入分组。优化器有时能自动做谓词下推,但开发者仍应写出正确、清晰的语义,且聚合条件通常无法下推到分组前。
五、常见错误
- 用
WHERE过滤聚合函数,忽略了处理阶段; - 把所有条件都堆到
HAVING,增加计算量并降低可读性; - 在
HAVING中引用既未分组也未聚合的列,结果依赖宽松模式或直接报错; - 以为
HAVING必须与GROUP BY同时出现。
六、常见误区与追问
| 条件类型 | 推荐位置 | 原因 |
|---|---|---|
| 单行状态、时间、租户 | WHERE | 聚合前过滤,减少输入行 |
SUM、COUNT 等聚合结果 | HAVING | 聚合后才有组级值 |
| 可提前判断的维度条件 | WHERE | 语义清楚且利于优化 |
| 组级阈值条件 | HAVING | 过滤分组结果 |
- 误区:HAVING 是 WHERE 的高级版。 二者过滤阶段不同,
WHERE过滤原始行,HAVING过滤分组聚合后的结果。 - 误区:聚合函数可以直接写在 WHERE。
WHERE发生在聚合之前,此时还没有SUM(amount)、COUNT(*)这类组级结果。 - 误区:所有条件都放 HAVING 也没问题。 虽然某些数据库能优化下推,但行级条件写在
WHERE更清晰,也更有机会提前减少扫描和分组成本。 - 追问:没有 GROUP BY 能不能写 HAVING? 可以,整个结果被看作一个组;常见于筛选总计结果,但不同数据库对非聚合列限制可能不同。
- 追问:别名能不能在 HAVING 中使用? 这取决于数据库实现;为了可移植性,面试中更稳妥的回答是直接写聚合表达式或包一层子查询。
- 追问:优化聚合查询的第一步是什么? 先把状态、时间范围、租户等行级条件放到
WHERE,再让GROUP BY和HAVING处理更小的数据集。
记忆钩子:
WHERE先拦住不该进组的行,HAVING再筛掉聚合后不合格的组。
七、加强记忆
WHERE 先筛行,GROUP BY 再成组,HAVING 后筛组。看到“状态、日期、单价”等单行条件先想 WHERE,看到“数量、总和、平均值”等聚合结果先想 HAVING;能够提前过滤的数据不要留到分组以后。