聚合函数有哪五种?

面试常问"SQL 聚合函数有哪五种",标准答案是 COUNT、SUM、AVG、MAX、MIN。但光背名字没用,实际写 SQL 时出问题的地方全在细节上——尤其是 COUNT 和 NULL 的关系,几乎每个人都栽过。

一、COUNT:数行数,但有两种写法

SELECT COUNT(*) FROM orders;        -- 统计总行数
SELECT COUNT(id) FROM orders;      -- 统计 id 非空的行数
SELECT COUNT(DISTINCT user_id) FROM orders;  -- 去重后计数

COUNT(*) 统计所有行,包括字段全为 NULL 的行。COUNT(字段) 只统计该字段非 NULL 的行。表里有 NULL 值时,两者结果会不一样,这是最常见的对不上数的原因。

另外 COUNT(1) 和 COUNT(*) 在主流数据库里执行计划一样,不用纠结性能。

二、SUM:求和,NULL 会被忽略

SELECT SUM(amount) FROM orders WHERE status = 1;

SUM 会跳过 NULL,如果所有值都是 NULL,结果是 NULL 而不是 0。所以前端展示金额时,通常要包一层:

SELECT COALESCE(SUM(amount), 0) FROM orders;

还有一点,SUM 对整数做累加时如果结果超出字段类型范围,MySQL 会报 BIGINT 溢出,大额统计建议先 CAST 成 DECIMAL。

三、AVG:平均值,分母是非 NULL 的行数

SELECT AVG(score) FROM exams;

如果有 10 条记录,其中 2 条 score 是 NULL,AVG 的分母是 8 而不是 10。这和很多人的直觉不同,业务上要算"缺考按 0 分计"时,得用 SUM/COUNT(*) 自己算:

SELECT SUM(score) / COUNT(*) FROM exams;

四、MAX 和 MIN:最大值、最小值

SELECT MAX(price), MIN(price) FROM products;

这两个除了数值,也能用在日期和字符串上。MAX(create_time) 拿最新记录,MIN(create_time) 拿最早记录,比 ORDER BY ... LIMIT 1 更直观(虽然执行效率要看索引)。

注意 MAX 忽略 NULL,如果整列都是 NULL,返回 NULL。

五、五个函数一起用的写法

SELECT
  COUNT(*)          AS total,
  SUM(amount)       AS total_amount,
  AVG(amount)       AS avg_amount,
  MAX(amount)       AS max_amount,
  MIN(amount)       AS min_amount
FROM orders
WHERE create_time >= '2026-01-01';

六、配合 GROUP BY 才是真正用法

SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 1000
ORDER BY total DESC;

这里要记住 WHERE 和 HAVING 的区别:WHERE 在分组前过滤原始行,HAVING 在分组后过滤聚合结果。把聚合条件写进 WHERE 会直接报错。

七、几个真实的坑

  • SELECT 里混入非聚合列又没写进 GROUP BY,在 MySQL 的 ONLY_FULL_GROUP_BY 模式下会直接报错,关掉这个模式虽然能跑,但结果是不确定的。
  • 聚合函数不能嵌套,SUM(AVG(x)) 这种写法不合法,要用子查询。
  • GROUP BY 之后 COUNT(*) 统计的是每组行数,不是总行数,需要总数得另外查。
  • 聚合查询在大表上很慢,记得给 GROUP BY 的字段加索引。