首页 / PostgreSQL 入门教程 / 聚合函数

PostgreSQL 入门教程

聚合函数

本教程共 50 篇 · 第 27 篇 · 更新于 2026-07-31 · 约 8 分钟阅读

PostgreSQLPostgreSQL 入门教程聚合函数COUNTSUMAVG

27. 聚合函数

本节目标:掌握 COUNT、SUM、AVG、MIN、MAX 五个聚合函数,理解它们对 NULL 的处理,分清 COUNT(*) 和 COUNT(列) 的差异,并学会用 FILTER 对部分行做聚合。

聚合函数(aggregate function)和普通函数不同:它一次吃掉一整列的很多行,吐回一个汇总值。比如”一共有多少行""金额加起来多少""平均年龄多少”。

还是用 orders 表来演示:

CREATE TABLE orders (
  id        INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id   INT,
  amount    NUMERIC(10,2),
  status    VARCHAR(20),
  created_at DATE
);

INSERT INTO orders (user_id, amount, status, created_at) VALUES
  (1, 99.00,  'paid',     '2025-02-01'),
  (2, 150.50, 'shipped',  '2025-04-10'),
  (3, NULL,   'pending',  '2025-06-11'),
  (1, 300.00, 'paid',     '2025-08-22'),
  (4, 75.25,  'cancelled','2025-10-05');

COUNT 计数

COUNT 用来数行数,有三种常见写法:

-- 数全部行(含 NULL 和重复行)
SELECT COUNT(*) FROM orders;

-- 数某列非空的行(第 3 行的 amount 是 NULL,不计入)
SELECT COUNT(amount) FROM orders;

-- 数某列去重后的非空值个数
SELECT COUNT(DISTINCT status) FROM orders;

注意第二行:表里有一行 amountNULLCOUNT(amount) 不把它算进去,所以结果是 4 而不是 5。

SUM 求和

SUM 把数值列的所有值加起来,同样忽略 NULL

-- 所有订单金额总和(NULL 那行自动跳过)
SELECT SUM(amount) FROM orders;
Note

如果没有任何一行匹配,SUM 返回 NULL 而不是 0。想让它返回 0,可配合 COALESCE(后续章节会讲)处理,例如 COALESCE(SUM(amount), 0)

AVG 求平均

AVG 计算某列的平均值,等价于”总和除以非空的个数”。

-- 平均订单金额:只按 4 个非空值算
SELECT AVG(amount) FROM orders;
Tip

AVG 的分母是非空行数,不是总行数。所以上面结果等于 SUM(amount) / COUNT(amount),而不是除以 5。这一点在”有缺失数据”的报表里特别容易算错。

MIN 与 MAX

MIN 取最小值,MAX 取最大值,数值、日期、文本都能用。它们同样忽略 NULL

-- 最小和最大订单金额
SELECT MIN(amount) AS min_amt,
       MAX(amount) AS max_amt
FROM orders;

对文本列,MIN/MAX 按字符顺序取首尾;对日期列,取最早和最晚。

用 FILTER 对部分行做聚合(PostgreSQL 特色)

有时你想”在一行里同时算出不同条件下的汇总”,不必写多个子查询。PostgreSQL 提供 FILTER (WHERE ...) 子句,只对满足条件的行参与聚合:

-- 一行里同时看:总订单数、已支付订单数、已支付总金额
SELECT COUNT(*)                       AS 总订单数,
       COUNT(*) FILTER (WHERE status = 'paid') AS 已支付笔数,
       SUM(amount) FILTER (WHERE status = 'paid') AS 已支付金额
FROM orders;

这样一次扫描就拿到多个口径,比写三次查询省事。

Tip

FILTER 写法和”把 CASE WHEN 包在聚合里”效果一样,但 FILTER 更直观。它对所有聚合函数都适用,做多维度报表时非常顺手。

聚合函数对 NULL 的处理

这是新手最容易踩的坑,记住一条总规则:聚合函数全部自动忽略 NULL,只有 COUNT(*) 例外——它数的是行,不看具体值,所以会包含 NULL 行。

-- 对比一下:COUNT(*) 含 NULL 行,COUNT(amount) 不含
SELECT COUNT(*)        AS total_rows,
       COUNT(amount)   AS non_null_amount,
       SUM(amount)     AS sum_amount,
       AVG(amount)     AS avg_amount
FROM orders;

COUNT(*) 与 COUNT(列) 的核心区别

这两者的差别,做报表时天天要用:

  • COUNT(*):统计所有行,不管某列是不是 NULL。
  • COUNT(列):只统计这一列非 NULL 的行。
-- 假设要统计"填了金额的订单数"和"总订单数"
SELECT COUNT(*)      AS 总订单数,
       COUNT(amount) AS 有金额的订单数
FROM orders;
Warning

需要”总行数”时务必用 COUNT(*)。用 COUNT(某列) 会悄悄漏掉 NULL 行,导致统计偏少。性能上 COUNT(*) 也通常更快,因为它不必检查具体列的值。

常见错误:在 WHERE 里用聚合函数

新手常犯的错:想在 WHERE 里直接筛”金额总和大于 100 的用户”,于是写 WHERE SUM(amount) > 100。这会直接报错——WHERE 处理的是”行”,那时还没算聚合,根本不认识 SUM。

正确做法有两种:

-- 做法一:用 HAVING(下一章会细讲)
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 100;

-- 做法二:用子查询先聚合,再在外层 WHERE 筛
SELECT *
FROM (
  SELECT user_id, SUM(amount) AS total
  FROM orders
  GROUP BY user_id
) t
WHERE total > 100;
Warning

记住铁律:WHERE 里不能出现聚合函数。要按汇总结果筛选,请用 HAVING 或子查询。

一个查询里算多个聚合

一个 SELECT 可以同时写多个聚合函数,它们各算各的,互不干扰:

SELECT COUNT(*)              AS 订单数,
       SUM(amount)           AS 总金额,
       AVG(amount)           AS 平均金额,
       MIN(amount)           AS 最小金额,
       MAX(amount)           AS 最大金额
FROM orders;

聚合也能先去重:SUM(DISTINCT …) / AVG(DISTINCT …)

除了 COUNT,SUM 和 AVG 也支持 DISTINCT,表示”先去重再汇总”:

-- 不同金额值的总和(重复的只算一次)
SELECT SUM(DISTINCT amount) FROM orders;
Note

实际里 SUM(DISTINCT ...) 用得少,多在特殊统计口径下才需要,别为了用而用。

字符串聚合:string_agg(PostgreSQL 特色)

除了数值汇总,PostgreSQL 还能把多行文本拼成一串,用 string_agg

-- 把每个用户的订单状态拼成一个逗号分隔的串
SELECT user_id,
       string_agg(status, ',' ORDER BY status) AS 状态列表
FROM orders
GROUP BY user_id
ORDER BY user_id;
Tip

string_agg 的第一个参数是要拼的列,第二个是分隔符,ORDER BY 写在里面控制拼接顺序。做”标签列表""状态轨迹”之类很方便,是报表里常用的小技巧。

Tip

学到这里你可能会问:聚合一次只能给一个全局值,能不能按类别分别聚合?答案是可以,那就是下一章要讲的 GROUP BY。简单说,GROUP BY 负责”分堆”,聚合函数负责”每堆算一笔”。单独用聚合是看全局汇总,配上 GROUP BY 就能看分组汇总。

小结

  • COUNT 计数、SUM 求和、AVG 平均、MIN/MAX 取极值。
  • 所有聚合函数(除 COUNT(*))都自动忽略 NULL。
  • FILTER (WHERE ...) 可只对部分行做聚合,是 PostgreSQL 做多口径报表的好帮手。
  • COUNT(*) 数全部行;COUNT(列) 只数该列非空的行;COUNT(DISTINCT 列) 数去重后的非空值。
  • WHERE 里不能用聚合函数,需筛选汇总结果时用 HAVING 或子查询。