SQL 数据分析 · 指南 · 2026年10月30日 · 9 分钟阅读

SQL 聚合函数:COUNT、SUM、AVG、MIN、MAX 用法与实例

许多小登机牌汇入机场显示屏上的一个大数字

聚合函数把很多行变成一个值。每天都会用到的是这五个:COUNT(多少个)、SUM(合计)、AVG(平均值)、MIN 和 MAX(最小值和最大值)。没有 GROUP BY 时,它们把整张表压缩成一行;有 GROUP BY 时,每组返回一行。大多数意外都可以用两条规则解释:除了 COUNT(*),所有聚合函数都会忽略 NULL;而 COUNT(*)、COUNT(列) 和 COUNT(DISTINCT 列) 数的是三种不同的东西。

下面的例子都在 Nomto SQL 课程的航空公司数据库上运行,使用 PostgreSQL 语法,引擎是 DuckDB(Nomto SQL 练习场背后的引擎),每个结果表都是真实输出。票价单位是美元。

一条查询用上五个聚合函数

ticket_flights 表里每位乘客的每个航段占一行,票价在 amount 列:

SELECT COUNT(*)              AS segments,
       SUM(amount)           AS revenue,
       ROUND(AVG(amount), 2) AS avg_fare,
       MIN(amount)           AS min_fare,
       MAX(amount)           AS max_fare
FROM ticket_flights;
segments revenue avg_fare min_fare max_fare
1045726 207669809.00 198.59 30.00 2033.00
函数 返回 适用类型
COUNT(*) 行数 任何表
COUNT(col) 非 NULL 值的个数 任何类型
SUM(col) 合计 数字
AVG(col) 算术平均值 数字
MIN(col)、MAX(col) 最小值、最大值 数字、文本、日期

COUNT(*)、COUNT(列) 和 COUNT(DISTINCT 列) 的区别

这是实际工作中最重要的区别,也是面试高频题:

SELECT COUNT(*)                        AS flights,
       COUNT(actual_departure)         AS departed,
       COUNT(DISTINCT arrival_airport) AS destinations,
       COUNT(DISTINCT aircraft_code)   AS models_used
FROM flights;
flights departed destinations models_used
33121 16773 104 8

COUNT(1) 和 COUNT(*) 结果相同,在任何现代数据库里都不会更快。选一种,保持一致就好。

COUNT(DISTINCT ...) 用来回答「有多少个不同的 X」,也就是常说的去重计数。tickets 表每张乘客机票占一行,多张机票可以属于同一个订单:

SELECT COUNT(*)                      AS tickets,
       COUNT(contact_data->>'email') AS with_email,
       COUNT(DISTINCT book_ref)      AS bookings
FROM tickets;
tickets with_email bookings
366733 199083 262788

要统计两列的不同组合,PostgreSQL 和 DuckDB 不接受 COUNT(DISTINCT a, b)(MySQL、Hive 和 Spark SQL 可以)。通用的做法是数 SELECT DISTINCT 的行数:

SELECT COUNT(*) AS routes
FROM (SELECT DISTINCT departure_airport, arrival_airport FROM flights) r;
routes
618

在 Hive 和 Spark 这类大数据引擎上,对超大表做 COUNT(DISTINCT) 可能很慢,因为所有不同的值最后要汇总到一起去重。常见的优化是先 GROUP BY 去重再 COUNT(*),就像上面这样;只需要近似值时,可以用 approx_count_distinct(Spark SQL、DuckDB)。

SUM 和 AVG

SUM 求和,AVG 用和除以非 NULL 值的个数。两者都只接受数字。按舱位等级统计,按平均票价排序:

SELECT fare_conditions,
       COUNT(*)              AS segments,
       ROUND(SUM(amount))    AS revenue,
       ROUND(AVG(amount), 2) AS avg_fare,
       MIN(amount)           AS min_fare,
       MAX(amount)           AS max_fare
FROM ticket_flights
GROUP BY fare_conditions
ORDER BY avg_fare DESC;
fare_conditions segments revenue avg_fare min_fare max_fare
Business 107642 55051796 511.43 91.00 2033.00
Comfort 17291 5661169 327.41 199.00 476.00
Economy 920793 146956844 159.6 30.00 745.00

平均值的平均值陷阱

平均票价是多少?对三个舱位的平均值再求平均,和对所有航段求平均,答案差得很远:

SELECT ROUND(AVG(avg_fare), 2) AS avg_of_averages,
       (SELECT ROUND(AVG(amount), 2) FROM ticket_flights) AS true_average
FROM (
  SELECT fare_conditions, AVG(amount) AS avg_fare
  FROM ticket_flights
  GROUP BY fare_conditions
) t;
avg_of_averages true_average
332.81 198.59

平均值的平均值让 17,291 个 Comfort 航段和 920,793 个 Economy 航段拥有同样的权重。永远对原始行求平均,或者从分组合计里算 SUM(amount) / SUM(segments)。

整数除法

在 PostgreSQL、SQL Server 和 SQLite 里,整数除以整数会丢掉余数:COUNT(*) / COUNT(*) 这类比率会悄悄变成 0 或 1。DuckDB 的 / 返回小数(7 / 2 是 3.5,7 // 2 是 3),MySQL、Hive 和 Spark SQL 的 / 也返回小数。先乘以 100.0,就像下面的例子那样,在任何数据库里都能得到正确答案。

MIN 和 MAX

MIN 和 MAX 适用于任何可以排序的类型:数字、日期和文本。

SELECT MIN(book_date)    AS first_booking,
       MAX(book_date)    AS last_booking,
       MIN(total_amount) AS smallest,
       MAX(total_amount) AS largest
FROM bookings;
first_booking last_booking smallest largest
2026-06-17 14:05:00 2026-08-11 18:00:00 34.00 12045.00

对文本来说,MIN(city) 返回按字母排第一的值(Abakan),MAX(city) 返回最后一个(Yuzhno-Sakhalinsk)。MAX 返回的是值,不是那一行:要看金额最大的是哪个订单,用 ORDER BY total_amount DESC LIMIT 1;需要每组各取一行时,用窗口函数(见 SQL 窗口函数)。

聚合函数和 NULL

SQL NULL 值处理在真实数据上逐一演示了这些规则,包括把未知值当成零之后平均值会偏多少。

聚合函数配合 GROUP BY 和 HAVING

GROUP BY 把行分成组,每个聚合函数在每组上各算一次。HAVING 再按聚合结果筛选组:

SELECT departure_airport, COUNT(*) AS departures
FROM flights
GROUP BY departure_airport
HAVING COUNT(*) >= 1500
ORDER BY departures DESC;
departure_airport departures
DME 3217
SVO 2981
LED 1900
VKO 1719

SQL 执行查询的顺序决定了两条规则:

详细说明和常见错误见 GROUP BY 和 HAVING 的区别。

条件聚合:FILTER 和 CASE

经常需要在一行里同时算出几个不同条件的计数。FILTER 子句只给某一个聚合函数加条件:

SELECT COUNT(*) FILTER (WHERE status = 'Cancelled') AS cancelled,
       COUNT(*) FILTER (WHERE status = 'Delayed')   AS delayed,
       SUM(CASE WHEN status = 'Cancelled' THEN 1 ELSE 0 END) AS cancelled_case,
       ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'Cancelled') / COUNT(*), 2) AS cancelled_pct
FROM flights;
cancelled delayed cancelled_case cancelled_pct
414 41 414 1.25

FILTER 在 PostgreSQL(9.4+)、DuckDB 和 SQLite(3.30+)里可用。MySQL、SQL Server 和 Hive 不支持;在那里用 SUM(CASE WHEN ... THEN 1 ELSE 0 END) 或 COUNT(CASE WHEN ... THEN 1 END),结果一样,cancelled_case 列就是证明。MySQL 和 Hive 里还可以写得更短:SUM(IF(status = 'Cancelled', 1, 0))。

同样的写法可以一次算出占比:

SELECT ROUND(SUM(amount)) AS revenue,
       ROUND(SUM(amount) FILTER (WHERE fare_conditions = 'Business')) AS business_revenue,
       ROUND(100.0 * SUM(amount) FILTER (WHERE fare_conditions = 'Business') / SUM(amount), 1) AS business_pct
FROM ticket_flights;
revenue business_revenue business_pct
207669809 55051796 26.5

公务舱航段只占约 10%,收入却占 26.5%。更多条件逻辑见 SQL CASE WHEN 用法。

JOIN 之后合计出错

聚合函数拿到什么行就算什么行。如果连接让一行匹配到多行,这一行就会被重复,SUM 会把它加好几次。连接机票之后再对订单金额求和:

SELECT SUM(b.total_amount) AS wrong_revenue,
       (SELECT SUM(total_amount) FROM bookings) AS right_revenue
FROM bookings b
JOIN tickets t ON t.book_ref = b.book_ref;
wrong_revenue right_revenue
343206192.00 207669809.00

一个三位乘客的订单有三张机票,所以它的金额被加了三次。查询没有报错,数字看起来也挺合理。正确做法是先在各自的粒度上聚合,再连接结果;只需要计数时,用 COUNT(DISTINCT ...)。这种「行数膨胀」的原理见 SQL JOIN 详解。

聚合函数不能嵌套

不能把一个聚合函数直接放进另一个里:SELECT MAX(COUNT(*)) FROM flights GROUP BY departure_airport 会报错「aggregate function calls cannot be nested」。先在子查询或 CTE 里聚合,再对结果聚合:

SELECT AVG(n) AS avg_tickets_per_booking, MAX(n) AS max_tickets
FROM (SELECT book_ref, COUNT(*) AS n FROM tickets GROUP BY book_ref) t;
avg_tickets_per_booking max_tickets
1.3955469808362635 5

平均每个订单 1.4 张机票,最多的有五张。多步聚合怎样写得清楚,见 SQL CTE 用法。

其他常用聚合函数

SELECT split_part(model, ' ', 1) AS maker,
       COUNT(*)   AS models,
       MAX(range) AS max_range,
       STRING_AGG(model, ', ' ORDER BY range DESC) AS line_up
FROM aircrafts
GROUP BY maker
ORDER BY max_range DESC;
maker models max_range line_up
Boeing 3 11100 Boeing 777-300, Boeing 767-300, Boeing 737-300
Airbus 3 6700 Airbus A319-100, Airbus A320-200, Airbus A321-200
Sukhoi 1 3000 Sukhoi Superjet-100
Bombardier 1 2700 Bombardier CRJ-200
Cessna 1 1200 Cessna 208 Caravan

每个聚合函数加上 OVER (...) 都能当作窗口函数使用,这样会保留每一行而不是把它们压缩掉:累计求和、组内占比就是这样算出来的。

常见问题

SQL 有哪些聚合函数?

标准的五个是 COUNT、SUM、AVG、MIN 和 MAX。大多数数据库还提供更多,比如 STRING_AGG、ARRAY_AGG、STDDEV、VARIANCE 和分位数函数。它们都把一组行变成一个值;有 GROUP BY 时,每组一个值。

COUNT(*) 和 COUNT(列) 有什么区别?

COUNT(*) 数行数。COUNT(列) 数该列不为 NULL 的行数。在 flights 表里,它们对 actual_departure 分别得到 33,121 和 16,773,因为还没起飞的航班没有起飞时间。

聚合函数会忽略 NULL 吗?

会,除了 COUNT(*) 之外都会。AVG 除以非 NULL 值的个数;一列全是 NULL(或者零行)时,SUM 返回 NULL 而不是 0。

WHERE 里能用聚合函数吗?

不能。WHERE 在分组之前筛选行,那时聚合结果还不存在。按聚合结果筛选要用 HAVING,或者在子查询里先算出来,再在外层查询里筛选。

不写 GROUP BY 能用聚合函数吗?

能。没有 GROUP BY 时,整张表(经过 WHERE 之后)就是一个组,结果只有一行。但不能在同一个 SELECT 里把这样的聚合和普通列混在一起。

SQL 怎样统计去重后的数量?

用 COUNT(DISTINCT 列)。多列组合去重计数时,在子查询里数 SELECT DISTINCT a, b 的行数,因为 COUNT(DISTINCT a, b) 只有 MySQL、Hive 和 Spark SQL 支持。

其他语言: English, Español, Português (Brasil), Tiếng Việt