35 HAVING
WHERE 在分组之前筛选单独的行;HAVING 在聚合计算完成后筛选分组。如果你想表达“订单数超过三笔的客户”,那是 HAVING——只有得到计数后,才能测试 count(*) > 3。如果你想表达“在 2024 年下过订单的客户”,那是 WHERE——年份是每一行的属性。
本章要点
- 使用
HAVING筛选分组 - 区分
WHERE(逐行)与HAVING(逐组) - 在同一查询中组合
WHERE和HAVING - 在
HAVING中使用聚合函数 - 为什么把谓词下推到
WHERE通常更快
示例
执行顺序有助于记住区别:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。把行谓词写在 WHERE 中,意图更清楚,也能减少进入分组的数据;PostgreSQL 也可以把合适的 HAVING 谓词下推。尽可能把更多筛选放进 WHERE。
HAVING 允许直接写聚合谓词:HAVING avg(amount) > 100、HAVING count(DISTINCT product_id) > 5。这些条件不能出现在 WHERE 中,因为聚合在那里还不存在。
最后一个查询在技术上有效,但有隐患:在 HAVING 中筛选非聚合列,效果与 WHERE 相同,却在流水线更晚的阶段进行。为了清晰起见,优先使用 WHERE;规划器通常也能把这个分组列谓词下推。
运行示例
运行方式(需要连接课程中的 orders_example 表):
常见陷阱
- 写
WHERE count(*) > 3——WHERE不允许使用聚合函数。请使用HAVING。 - 写
HAVING amount > 100只有在amount已被分组或函数依赖于分组时才有效。要筛选单独的输入行,请使用WHERE amount > 100。规划器通常会重写这一点,但读者会感到困惑。 - 忘记
HAVING在GROUP BY之后运行,因此引用的任何列都必须是GROUP BY列,或位于聚合函数内。 - 在
count(*) FILTER (WHERE ...)这种逻辑中使用HAVING而不是WHERE。FILTER针对每个聚合,位于SELECT中;HAVING针对每个分组,决定保留哪些组。
如果你想要“按某个聚合值排名的前 N 个分组”,可以组合 GROUP BY + ORDER BY agg DESC + LIMIT N——不需要 HAVING。HAVING 用于谓词(“至少 3 个”),而不是排名(“前 3 个”)。
修改阈值:HAVING sum(total) < 50,看看结果集如何变化。然后把筛选移到 WHERE:WHERE total > 20 GROUP BY customer_id;,观察计数下降,因为进入分组的行变少了。
下载
SQL 文件:having.sql · 全部示例文件 · 准备工作