# HAVING

> 使用 HAVING 筛选聚合分组，并区分分组谓词与行筛选。

---

LLMS 索引： [llms.txt](/llms.txt)

---

`WHERE` 在分组*之前*筛选单独的行；`HAVING` 在聚合计算完成后筛选*分组*。如果你想表达“订单数超过三笔的客户”，那是 `HAVING`——只有得到计数后，才能测试 `count(*) > 3`。如果你想表达“在 2024 年下过订单的客户”，那是 `WHERE`——年份是每一行的属性。

## 本章要点 {#learn}

- 使用 `HAVING` 筛选分组
- 区分 `WHERE`（逐行）与 `HAVING`（逐组）
- 在同一查询中组合 `WHERE` 和 `HAVING`
- 在 `HAVING` 中使用聚合函数
- 为什么把谓词下推到 `WHERE` 通常更快

## 示例 {#example}

```sql {num="35.1" caption="HAVING"}
-- HAVING with one aggregate condition
SELECT customer_id, count(*) AS order_count, sum(total) AS total_spent
FROM orders_example
GROUP BY customer_id
HAVING sum(total) > 30
ORDER BY customer_id;

-- HAVING with multiple conditions
SELECT customer_id, count(*) AS order_count, sum(total) AS total_spent
FROM orders_example
GROUP BY customer_id
HAVING count(*) >= 1 AND sum(total) > 30;

-- WHERE then HAVING: filter rows first, group, then filter groups
SELECT customer_id, count(*) AS order_count
FROM orders_example
WHERE total > 20                      -- per-row filter
GROUP BY customer_id
HAVING count(*) >= 1;                 -- per-group filter

-- HAVING without aggregates is legal but pointless — same as WHERE
SELECT customer_id
FROM orders_example
GROUP BY customer_id
HAVING customer_id > 50;              -- could just be 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`；规划器通常也能把这个分组列谓词下推。

## 运行示例 {#run}

运行方式（需要连接课程中的 `orders_example` 表）：

```text
$ psql -f source/having.sql postgres
 customer_id | order_count | total_spent
-------------+-------------+-------------
           1 |           2 |       80.00
(1 row)
 ...
```

## 常见陷阱 {#pitfalls}

- 写 `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` 针对每个分组，决定保留哪些组。

> [!TIP] 提示
> 如果你想要“按某个聚合值排名的前 N 个分组”，可以组合 `GROUP BY` + `ORDER BY agg DESC` + `LIMIT N`——不需要 `HAVING`。`HAVING` 用于谓词（“至少 3 个”），而不是排名（“前 3 个”）。

> [!NOTE] 动手练习
> 修改阈值：`HAVING sum(total) < 50`，看看结果集如何变化。然后把筛选移到 `WHERE`：`WHERE total > 20 GROUP BY customer_id;`，观察计数下降，因为进入分组的行变少了。

## 下载 {#downloads}

SQL 文件：[having.sql](/examples/source/having.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
