聚合函数
本教程共 50 篇 · 第 27 篇 · 更新于 2026-07-31 · 约 8 分钟阅读
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;
注意第二行:表里有一行 amount 是 NULL,COUNT(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;
TipAVG 的分母是非空行数,不是总行数。所以上面结果等于
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 或子查询。