一、什么是聚合函数?
假设你是一家小商店的老板,每天会记录 “每笔订单的金额、购买的商品、下单时间”(这些记录存在数据库的 “订单表” 里)。
如果想知道 “这个月一共卖了多少笔订单”“平均每笔订单花了多少钱”,总不能手动数、手动算吧?
这时候就需要聚合函数:把表中的 “一堆数据”(多条订单记录)聚合成 “一个结果”(比如总订单数、平均金额),核心是 “从多到一” 的计算。
二、5 个必学聚合函数:用法 + 例子 + 注意事项
下面用 “订单表(orders)” 为例,表中有 3 个字段:order_id(订单编号)、user_id(顾客编号)、amount(订单金额,单位:元)、create_time(下单时间),先看表中数据(方便理解例子):
| order_id | user_id | amount | create_time |
| 1 | 101 | 50 | 2025-05-01 |
| 2 | 102 | 150 | 2025-05-01 |
| 3 | 101 | 80 | 2025-05-02 |
| 4 | 103 | NULL | 2025-05-02 |
| 5 | 102 | 120 | 2025-05-03 |
1. COUNT ():统计 “有多少条记录”(最常用,含 COUNT (1))
作用:数表中 “符合条件的记录数量”,比如 “有多少笔订单”“有多少个顾客下过单”。
它有 4 种常用写法(新增COUNT(1)),区别要分清!
(1)COUNT (*):统计 “所有记录”(包括空值)
- 不管字段里有没有空值(比如上面订单 4 的amount是 NULL),只要是一条完整的记录,就会被算进去。
- 例子:统计订单表一共有多少笔订单(包括金额为空的订单 4)
SELECT COUNT(*) AS 总订单数 FROM orders;
- 结果:总订单数 = 5(因为表中一共 5 条订单记录,哪怕订单 4 金额是空的,也算一笔订单)。
(2)COUNT (1):统计 “所有记录”(和 COUNT (*) 功能几乎一样)
- 原理:1是一个 “常量”,SQL 会给每一条记录 “临时加一个值为 1 的虚拟字段”,然后统计这个虚拟字段的数量 —— 只要是一条记录,就会有一个 “1”,所以最终结果和COUNT(*)相同,也包括空值记录。
- 例子:同样统计订单表总订单数(结果和COUNT(*)一致)
SELECT COUNT(1) AS 总订单数 FROM orders;
- 结果:总订单数 = 5(和COUNT(*)一样,包含订单 4)。
- COUNT (1) 和 COUNT (*) 的区别与联系:
- 联系:99% 的场景下结果完全相同,都统计所有记录(包括空值),执行效率也几乎没有差异(现代数据库会自动优化,比如 MySQL、PostgreSQL 对两者的优化逻辑一致)。
- 区别:原理不同 ——COUNT(*)是直接统计记录行数,COUNT(1)是统计 “虚拟常量字段” 的数量;极端场景(比如表没有任何字段,仅用于存空记录)下COUNT(*)可用,COUNT(1)也可用,但实际业务中几乎遇不到。
- 小白建议:不用纠结,两者随便用,想简单记就记COUNT(*),如果看到别人写COUNT(1),知道它和COUNT(*)功能一样就行。
(3)COUNT (字段名):统计 “该字段不为空的记录”
- 只数 “某个字段有值的记录”,如果字段是 NULL(空),就不算。
- 例子:统计 “金额有填写的订单” 有多少笔(排除订单 4)
SELECT COUNT(amount) AS 有金额的订单数 FROM orders;
- 结果:有金额的订单数 = 4(订单 4 的amount是 NULL,被排除了)。
(4)COUNT (DISTINCT 字段名):统计 “该字段不重复且不为空的记录”
- 先去重(比如同一个顾客下了多笔订单,只算一次),再统计数量,同时排除 NULL。
- 例子:统计 “有多少个不同的顾客下过单”(顾客 101 下了 2 笔,只算 1 次)
SELECT COUNT(DISTINCT user_id) AS 不同顾客数 FROM orders;
- 结果:不同顾客数 = 3(顾客 101、102、103,共 3 人,订单 4 的user_id是 103,不为空,会被算进去)。
COUNT () 系列注意事项:
- “空值(NULL)” 不是 “0” 或 “空字符串”:比如订单 4 的amount是 “没填”,不是 “0 元”,所以COUNT(amount)会排除它,但COUNT(*)和COUNT(1)会包含它。
- 不要用COUNT(字段名)统计总记录数:比如想算总订单数,用COUNT(*)或COUNT(1),别用COUNT(amount),因为会漏掉字段为空的记录。
2. SUM ():计算 “数值的总和”(只对数字有效)
作用:把 “数值型字段” 的所有值加起来,比如 “所有订单的总金额”“某个顾客的总消费”。
- 注意:只能算数字(比如amount是金额,是数字),不能算文字(比如user_id是顾客编号,虽然是数字,但本质是标识,一般不用 SUM 加)。
- 自动忽略 NULL:如果字段是 NULL(比如订单 4 的amount),计算时会跳过,不会把 NULL 当 0 算。
例子 1:计算所有订单的 “总金额”(排除订单 4 的 NULL)
SELECT SUM(amount) AS 订单总金额 FROM orders;
- 计算过程:50 + 150 + 80 + 120 = 400(订单 4 的 NULL 被跳过)
- 结果:订单总金额 = 400。
例子 2:计算 “顾客 102” 的总消费(他下了订单 2 和 5)
SELECT SUM(amount) AS 顾客102总消费
FROM orders
WHERE user_id = 102; -- 只看顾客102的订单
- 计算过程:150 + 120 = 270
- 结果:顾客102总消费 = 270。
SUM () 注意事项:
- 只能用于 “数值字段”:如果对user_id用 SUM(比如SUM(user_id)),虽然语法不报错,但结果没意义(101+102+101+103+102=509,这个数字没有业务价值)。
3. AVG ():计算 “数值的平均值”(只对数字有效)
作用:求 “数值型字段” 的平均值,公式是 “总和 ÷ 不为空的记录数”(不是总记录数)。
- 自动忽略 NULL:和 SUM 一样,NULL 会被跳过,不参与计算。
例子:计算所有订单的 “平均金额”(排除订单 4 的 NULL)
SELECT AVG(amount) AS 平均订单金额 FROM orders;
- 计算过程:总和(400) ÷ 有金额的订单数(4) = 100
- 结果:平均订单金额 = 100。
易错点提醒:
- 不是 “总和 ÷ 总记录数”:如果按总记录数 5 算,400÷5=80,这是错的!因为 AVG 会自动排除 NULL,只除以 “有值的记录数”。
4. MAX ():找 “最大值”(支持数字、日期、文字)
作用:从 “字段” 中找最大的值,比如 “最大的订单金额”“最新的下单时间”。
- 支持多种类型:
- 数字:找最大的数(比如金额最大);
- 日期:找最新的时间(比如最近的下单时间);
- 文字:按字典顺序找最后一个(比如用户名最大,用得少)。
例子 1:找 “最大的订单金额”
SELECT MAX(amount) AS 最大订单金额 FROM orders;
- 结果:最大订单金额 = 150(订单 2 的金额 150 是最大的)。
例子 2:找 “最新的下单时间”
SELECT MAX(create_time) AS 最新下单时间 FROM orders;
- 结果:最新下单时间 = 2025-05-03(订单 5 的时间是最晚的)。
5. MIN ():找 “最小值”(和 MAX 相反)
作用:从 “字段” 中找最小的值,用法和 MAX 完全相反,支持数字、日期、文字。
例子 1:找 “最小的订单金额”(排除 NULL)
SELECT MIN(amount) AS 最小订单金额 FROM orders;
- 结果:最小订单金额 = 50(订单 1 的金额 50 是最小的)。
例子 2:找 “最早的下单时间”
SELECT MIN(create_time) AS 最早下单时间 FROM orders;
- 结果:最早下单时间 = 2025-05-01(订单 1 和 2 的时间是最早的)。
三、进阶:聚合函数 + GROUP BY(分组汇总)
前面的例子都是 “对整个表做汇总”(比如所有订单的总金额),但实际中经常需要 “按类别汇总”—— 比如 “按日期分组,统计每天的订单金额”“按顾客分组,统计每个顾客的总消费”。
这时候就要用GROUP BY 分组字段,意思是 “把表按某个字段分成几组,每组分别用聚合函数计算”。
例子 1:按 “下单日期” 分组,统计每天的订单数(用 COUNT (1))和总金额
需求:想知道 5 月 1 日、2 日、3 日每天各有多少笔订单,总金额是多少(这里用COUNT(1)统计订单数)。
SELECT
create_time AS 下单日期, -- 按“下单日期”分组
COUNT(1) AS 当日订单数, -- 每组(每天)的订单数,用COUNT(1)
SUM(amount) AS 当日总金额 -- 每组(每天)的总金额
FROM orders
GROUP BY create_time; -- 关键:按日期分组
- 结果:
| 下单日期 | 当日订单数 | 当日总金额 |
|------------|------------|------------|
| 2025-05-01 | 2 | 200(50+150) |
| 2025-05-02 | 2 | 80(只有订单 3 的 80,订单 4 是 NULL) |
| 2025-05-03 | 1 | 120 |
- 说明:这里用COUNT(1)和用COUNT(*)的结果完全一样,都是统计每天的总订单数。
例子 2:按 “顾客” 分组,统计每个顾客的订单数(用 COUNT (*))和总消费
SELECT
user_id AS 顾客编号,
COUNT(*) AS 顾客订单数, -- 每个顾客的总订单数(包括金额NULL的)
SUM(amount) AS 顾客总消费 -- 每个顾客的总金额(排除NULL)
FROM orders
GROUP BY user_id; -- 按顾客编号分组
- 结果:
| 顾客编号 | 顾客订单数 | 顾客总消费 |
|----------|------------|------------|
| 101 | 2 | 130(50+80) |
| 102 | 2 | 270(150+120) |
| 103 | 1 | NULL(订单 4 金额是 NULL,SUM 忽略后为 NULL) |
四、再进阶:HAVING(筛选分组后的结果)
用GROUP BY分组后,想筛选 “符合条件的组”,比如 “找出总消费超过 100 元的顾客”,这时候不能用WHERE(WHERE是筛选分组前的原始数据),必须用HAVING(专门筛选分组后的结果)。
例子:找出 “总消费超过 100 元” 的顾客,同时统计他们的订单数(用 COUNT (1))
SELECT
user_id AS 顾客编号,
COUNT(1) AS 顾客订单数, -- 用COUNT(1)统计订单数
SUM(amount) AS 顾客总消费
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 100; -- 筛选总消费>100的组(顾客)
- 结果:
| 顾客编号 | 顾客订单数 | 顾客总消费 |
|----------|------------|------------|
| 101 | 2 | 130 |
| 102 | 2 | 270 |
- 解释:顾客 103 的总消费是 NULL,不满足 “>100”,所以被排除;这里COUNT(1)统计的订单数和COUNT(*)一致。
五、小白必记的 4 个核心原则
- COUNT (*) 和 COUNT (1) 几乎无差别:都统计所有记录(包括空值),结果和效率差不多,不用纠结,随便用。
- 聚合函数只能用于 “汇总”:不能单独用在WHERE里(比如WHERE SUM(amount) > 100是错的,必须用HAVING)。
- 自动忽略 NULL:除了COUNT(*)和COUNT(1),其他聚合函数(SUM、AVG、MAX、MIN、COUNT (字段))都会跳过 NULL 值,不参与计算。
- GROUP BY 和 SELECT 要对应:用GROUP BY 字段A时,SELECT里只能有 “分组字段 A” 和 “聚合函数”(比如SELECT user_id, SUM(amount)是对的,SELECT user_id, order_id是错的,因为order_id不是分组字段,也不是聚合函数)。
六、总结:什么时候用哪个函数?
| 需求场景 | 用哪个函数? | 需求场景 | 用哪个函数? | |
| 统计所有记录数(最常用) | COUNT (*) 或 COUNT (1) | 找最大的值(最大金额、最新时间) | MAX (字段) | |
| 统计有多少个不同的数值 | COUNT (DISTINCT 字段) | 找最小的值(最小金额、最早时间) | MAX (字段) | |
| 统计某个字段有值的记录数 | COUNT (字段名) | 找最小的值(最小金额、最早时间) | MIN (字段) | |
| 计算总和(金额、数量等) | SUM (字段) | 按类别汇总(每天、每个顾客) | GROUP BY + 聚合函数 | |
| 计算平均值(平均金额等) | AVG (字段) |
1899

被折叠的 条评论
为什么被折叠?



