SQL 中的聚合函数用于对一组值执行统一计算,并将多行数据归纳为单个结果值。在 SELECT 语句中使用聚合函数,可以快速完成计数、求和、求平均值、查找最大值和最小值等统计任务。对于订单、用户、商品、日志等常见业务数据,聚合函数能够帮助开发者从明细数据中提炼出更有价值的汇总信息。

常用聚合函数的语义与适用场景
在 SQL 查询中,聚合函数通常作用于某一列或整个结果集。它们并不返回每一行明细,而是返回经过计算后的汇总值。常见的聚合函数包括 COUNT()、SUM()、AVG()、MAX() 和 MIN()。这些函数在大多数关系型数据库中都有稳定支持,是数据统计查询中最基础也最常用的工具。
不同聚合函数适用于不同的分析目标。若需要了解记录数量,可以使用 COUNT();若需要计算总金额、总销量等数值合计,可以使用 SUM();若需要得到平均水平,可以使用 AVG();若需要寻找最高值或最低值,则可以使用 MAX() 和 MIN()。理解这些函数的差异,有助于写出更符合业务需求的统计语句。
| 函数名称 | 功能说明 | 适用数据类型 |
|---|---|---|
COUNT() | 统计符合条件的行数 | 所有类型 |
SUM() | 计算数值列的总和 | 数值类型 |
AVG() | 计算数值列的平均值 | 数值类型 |
MAX() | 返回列中的最大值 | 数值、日期、字符串类型 |
MIN() | 返回列中的最小值 | 数值、日期、字符串类型 |
使用聚合函数时,还需要注意空值对结果的影响。多数聚合函数在计算时会忽略 NULL 值。例如,COUNT(column) 不会统计该列为 NULL 的行,而 COUNT(*) 则统计结果集中的所有行。类似地,SUM() 和 AVG() 也会跳过 NULL 值。因此,在分析数据质量或统计完整记录数时,应明确选择适合的计数方式。
在 SELECT 中直接使用聚合函数
如果查询目标是对整张表或满足条件的一组行做整体统计,而不需要按维度分组,可以直接在 SELECT 子句中调用聚合函数。此时,整个查询结果集会被视为一个整体,聚合函数会基于这个整体返回一行汇总结果。这种写法适合制作总览指标,例如总订单数、总金额、平均金额等。
假设存在一张订单表 order_table,其中包含订单编号 order_id、用户编号 user_id、订单金额 order_amount 和订单日期 order_date。如果需要一次性查看全部订单的数量、总金额、平均金额、最大金额和最小金额,可以在同一条 SELECT 语句中同时使用多个聚合函数。
-- 统计全部订单的核心指标
SELECT
COUNT(order_id) AS total_order_count,
SUM(order_amount) AS total_order_amount,
AVG(order_amount) AS avg_order_amount,
MAX(order_amount) AS max_order_amount,
MIN(order_amount) AS min_order_amount
FROM order_table;
在上述语句中,AS 关键字用于为聚合结果指定别名。别名的作用不仅是让结果列名称更易读,也能方便应用程序或报表工具读取对应字段。若不使用别名,数据库通常会生成一个由函数名或表达式组成的默认列名,这在复杂查询中不够直观。
如果只需要统计满足特定条件的数据,可以在聚合前使用 WHERE 子句过滤原始行。WHERE 会在聚合计算之前筛选数据,因此最终参与统计的只有符合条件的记录。例如,只统计订单金额大于零的订单,可以这样写。
-- 统计订单金额大于零的订单
SELECT
COUNT(order_id) AS valid_order_count,
SUM(order_amount) AS valid_total_amount,
AVG(order_amount) AS valid_avg_amount
FROM order_table
WHERE order_amount > 0;
直接在 SELECT 中使用聚合函数时,应避免同时出现没有参与聚合的普通列。因为聚合结果只有一行,而普通列可能对应多行数据,数据库无法确定应该返回哪一行的值。如果确实需要普通列和聚合结果一起出现,通常必须引入分组查询。
使用 GROUP BY 将数据分组后计算
很多统计需求并不是只得到一个总数,而是希望按照某个维度分别计算。例如,统计每个用户的订单数量,或者统计每个订单日期下的消费总额。这时就需要使用 GROUP BY 子句。GROUP BY 会把指定字段值相同的行归为一组,然后对每一组分别执行聚合计算。
以订单表为例,如果需要统计每个用户的订单数量和总消费金额,可以按照 user_id 进行分组。分组后,每个用户对应一组订单数据,聚合函数会在每个用户组内分别计算。
-- 按用户统计订单数量和消费总额
SELECT
user_id,
COUNT(order_id) AS user_order_count,
SUM(order_amount) AS user_total_amount
FROM order_table
GROUP BY user_id;
在分组查询中,有一个非常重要的规则:如果 SELECT 子句中同时包含普通列和聚合函数,那么普通列必须出现在 GROUP BY 子句中。因为普通列代表分组维度,数据库需要知道每一行结果对应哪一组。如果普通列没有参与分组,查询语义就会变得不明确,多数数据库会直接报错。
除了单字段分组,还可以使用多个字段进行更细粒度的分组。例如,同时按照用户和订单日期统计每个用户在每个订单日期下的订单数量与订单金额。这种写法适合分析用户在不同日期下的行为差异。
-- 按用户和订单日期分组统计
SELECT
user_id,
order_date,
COUNT(order_id) AS daily_order_count,
SUM(order_amount) AS daily_total_amount
FROM order_table
GROUP BY user_id, order_date;
多字段分组时,数据库会把多个字段的组合值作为分组依据。只有当所有分组字段的值都相同时,相关行才会被归入同一组。因此,分组字段越多,统计粒度越细;分组字段越少,统计结果越概括。实际开发中,应根据业务分析目标选择合适的分组维度。
使用 HAVING 过滤聚合结果
在聚合查询中,WHERE 子句用于过滤原始行,而 HAVING 子句用于过滤聚合后的结果。换句话说,WHERE 作用于分组之前,决定哪些行可以参与聚合;HAVING 作用于分组之后,决定哪些分组可以出现在最终结果中。当过滤条件涉及聚合函数时,通常需要使用 HAVING。
例如,需要查询总消费金额超过一千的用户,并显示这些用户的订单数量和总消费金额。由于过滤条件是对每个用户分组求和后的结果进行判断,因此不能在 WHERE 中直接使用 SUM(order_amount),而应使用 HAVING。
-- 筛选总消费金额超过一千的用户
SELECT
user_id,
COUNT(order_id) AS user_order_count,
SUM(order_amount) AS user_total_amount
FROM order_table
GROUP BY user_id
HAVING SUM(order_amount) > 1000;
理解 WHERE 与 HAVING 的区别,有助于写出更高效的查询。如果能够在分组前就排除大量无关数据,应优先使用 WHERE,因为它可以减少参与聚合计算的行数。如果必须依据聚合结果进行筛选,再使用 HAVING。两者并不冲突,可以在同一条查询中配合使用。
从逻辑顺序上看,一条包含分组和聚合的查询通常会先确定数据来源,再通过 WHERE 过滤原始行,然后进行分组,再使用聚合函数计算每组结果,最后通过 HAVING 筛选分组。掌握这个处理顺序,可以更快定位查询结果不符合预期的原因。
使用聚合函数时需要注意的细节
聚合函数虽然写法简洁,但在实际使用中有很多细节需要关注。首先是空值处理。由于多数聚合函数会忽略 NULL,所以统计结果可能与直觉不同。若某列存在大量空值,使用 COUNT(column) 得到的数量会小于 COUNT(*)。如果业务目标是统计所有记录行数,应优先使用 COUNT(*)。
COUNT(*)统计结果集中的所有行,不关心列值是否为NULL。COUNT(column)只统计指定列不为NULL的行。SUM()和AVG()通常只适用于数值类型,并会忽略NULL值。- 当
SELECT中同时出现普通列和聚合函数时,普通列必须加入GROUP BY。 WHERE过滤原始行,HAVING过滤聚合后的分组结果。- 不同数据库会提供扩展聚合函数,例如字符串拼接类函数,具体用法需要参考对应数据库文档。
其次,聚合函数可以嵌套使用,但嵌套时通常需要借助子查询或派生表来表达清晰的计算层次。例如,先计算每个用户的平均订单金额,再计算所有用户平均订单金额的平均值。这种需求不能简单地在外层直接对明细数据求平均,因为那样得到的是所有订单金额的平均值,而不是每个用户平均值的平均值。
-- 先计算每个用户的平均订单金额,再计算这些平均值的平均值
SELECT AVG(user_avg_amount) AS avg_of_user_avg
FROM (
SELECT
user_id,
AVG(order_amount) AS user_avg_amount
FROM order_table
GROUP BY user_id
) AS user_avg_table;
使用派生表进行嵌套聚合的好处是逻辑层次清楚。内层查询先完成一次分组聚合,得到每个用户的平均订单金额;外层查询再把内层结果作为新的数据源继续聚合。这种方式不仅易于理解,也便于后续添加过滤条件、排序或连接其他表。
总体而言,聚合函数是 SQL 数据统计能力的重要组成部分。熟练使用 COUNT()、SUM()、AVG()、MAX() 和 MIN(),并理解它们与 WHERE、GROUP BY、HAVING 之间的关系,可以显著提升查询表达能力。在编写聚合查询时,建议先明确统计对象、分组维度和过滤条件,再选择合适的聚合函数,这样能够更快写出准确、清晰且易于维护的 SQL 语句。