# COUNT、SUM 与 AVG

> 统计数量、求和、求平均值并收集值，同时处理 NULL 和空输入。

---

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

---

*聚合函数*会对多行计算出一个值。最常见的五种——`count`、`sum`、`avg`、`min`、`max`——是基础函数。它们处理 NULL 的方式略有不同：`count(*)` 统计每一行，包括含 NULL 的行；`count(col)` 统计 `col` 的非 NULL 值；`sum`/`avg`/`min`/`max` 完全忽略 NULL。还有几十种其他函数（`stddev`、`string_agg`、`array_agg`、`bool_and`、`bool_or`、`jsonb_agg`……），每种聚合函数都有自己的行为：例如，`array_agg` 和 `jsonb_agg` 会保留 NULL 输入。

## 本章要点 {#learn}

- `count(*)`、`count(col)` 与 `count(DISTINCT col)` 的区别
- `sum`、`avg`、`min`、`max` 处理数值及其他类型
- 聚合函数如何处理 NULL
- 使用 `string_agg` 和 `array_agg` 构建列表
- 使用 `FILTER (WHERE ...)` 为每个聚合设置条件

## 示例 {#example}

```sql {num="33.1" caption="COUNT、SUM 与 AVG"}
-- Basic counts
SELECT count(*) AS total_rows FROM fruits;
SELECT count(name) AS non_null_names FROM fruits;
SELECT count(DISTINCT name) AS unique_names FROM fruits;

-- Sum and average
SELECT sum(id) AS sum_id, avg(id) AS avg_id, round(avg(id), 2) AS avg_round
FROM fruits;

-- Min and max work on numbers, text, dates
SELECT min(name) AS min_name, max(name) AS max_name FROM fruits;

-- Build a string or array from a column
SELECT string_agg(name, ', ' ORDER BY id) AS names_csv,
       array_agg(name ORDER BY id)         AS names_arr
FROM fruits;

-- FILTER: per-aggregate WHERE; replaces messy CASE WHEN tricks
SELECT count(*) AS total,
       count(*) FILTER (WHERE id <= 3) AS low,
       count(*) FILTER (WHERE id > 3)  AS high,
       sum(id)  FILTER (WHERE id > 3)  AS high_sum
FROM fruits;
```

`count(*)` 统计行数——快速而清晰。`count(col)` 会跳过 `col` 的 NULL 值；如果你想知道“有多少行的 `email` 非 NULL”，这就是正确的选择。`count(DISTINCT col)` 会先去重；因为必须先物化去重后的集合，所以更慢。

`sum` 返回总和；如果没有行则返回 NULL（不是 0——这很令人意外，但符合标准）。没有行时，`avg` 返回 NULL；对于整数输入，`avg` 返回 `numeric`，因为整数的平均值很少还是整数。`min` 和 `max` 适用于任何可排序的内容，包括文本和时间戳。

`string_agg` 和 `array_agg` 是 PostgreSQL 的“连接值”聚合函数——当你想生成列表时，它们正是合适的工具。聚合内部可选的 `ORDER BY` 控制连接顺序；默认的无序行为无法保证顺序。

`FILTER (WHERE ...)` 是 SQL 标准规定的、为单个聚合应用条件的方式。它取代了旧式的 `sum(CASE WHEN cond THEN x END)` 写法——结果相同，但清楚得多。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/count-sum-avg.sql postgres
 total_rows
------------
          8
(1 row)
 ...
   names_csv
-----------------------------------------------------------
 apricot, blueberry, chestnut, damson, elderberry, fruit_1, fruit_2, fruit_3
(1 row)

 total | low | high | high_sum
-------+-----+------+----------
     8 |   3 |    5 |      315
(1 row)
```

## 常见陷阱 {#pitfalls}

- 对零行求 `sum` 时，返回 `NULL` 而不是 `0`。如果需要 0，请使用 `COALESCE(sum(x), 0)`。
- 整数列上的 `avg` 会产生 `numeric` 结果；如果想显示四舍五入后的值，可以把它包在 `round` 中。
- `count(col)` 跳过 NULL 是一个特性，但会让初学者感到意外。`count(*)` 统计行数；其他形式都是“非 NULL 计数”。
- 不使用 `GROUP BY` 的聚合会将整个结果聚合为一行。使用 `GROUP BY` 则每组得到一行（下一课介绍）。

> [!TIP] 提示
> 在一次扫描中组合聚合函数和 `FILTER`，即可同时得到许多统计结果。`SELECT count(*), count(*) FILTER (WHERE is_active), avg(amount) FILTER (WHERE status = 'paid'), max(created_at) FROM ...` 只扫描一次，却能给出许多答案。

> [!NOTE] 动手练习
> 给 `fruits` 增加一个价格列：`ALTER TABLE fruits ADD COLUMN price numeric(10,2);`（然后用 `UPDATE` 修改一些价格），再对它运行 `sum`、`avg`、`min`、`max`。然后试试 `SELECT string_agg(name, ', ' ORDER BY id DESC) FROM fruits;`，看看顺序反过来了。

## 下载 {#downloads}

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