SQL GROUP BY 和聚合函数:汇总你的数据

Published: 2026-04-08

GROUP BY 会把在一个或多个列中具有相同值的多行折叠为一行汇总结果。配合 COUNTSUMAVGMINMAX 等聚合函数,它构成了几乎所有报表和分析查询的基础。所有 SQL 数据库都支持 GROUP BY,包括 SQLite、PostgreSQL、MySQL、SQL Server 和 Oracle。

示例数据

下面所有示例都使用两张表:

employees 表:

id name department hire_date salary
1 Alice Engineering 2023-01-15 95000
2 Bob Engineering 2023-06-01 85000
3 Carol Engineering 2024-03-10 85000
4 Dave Sales 2023-02-20 72000
5 Eve Sales 2023-09-05 68000
6 Frank Sales 2024-01-15 65000
7 Grace Marketing 2023-04-01 82000
8 Henry Marketing 2024-06-01 70000

orders 表:

id customer product category quantity unit_price order_date status
1 Acme Corp Laptop Pro Electronics 3 1299.99 2026-01-05 delivered
2 Acme Corp Wireless Mouse Electronics 10 29.99 2026-01-12 delivered
3 Globex Office Chair Furniture 5 249.99 2026-01-18 delivered
4 Globex Standing Desk Furniture 2 599.99 2026-01-20 shipped
5 Initech Notebook Pack Stationery 20 12.99 2026-02-03 delivered
6 Initech Pen Set Stationery 15 8.99 2026-02-07 delivered
7 Acme Corp Monitor 27" Electronics 4 399.99 2026-02-14 delivered
8 Umbrella Ltd Laptop Pro Electronics 2 1299.99 2026-02-20 cancelled
9 Umbrella Ltd Desk Lamp Furniture 8 39.99 2026-03-01 delivered
10 Globex Wireless Mouse Electronics 12 29.99 2026-03-05 shipped
11 Initech Monitor 27" Electronics 1 399.99 2026-03-10 delivered
12 Acme Corp Standing Desk Furniture 1 599.99 2026-03-15 processing
在 DbGate Lite 中探索数据集

GROUP BY 语法

SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition
GROUP BY column1
HAVING aggregate_condition
ORDER BY column1;
  • GROUP BY —— 一个或多个列,这些列的不同取值组合定义了每个分组
  • 聚合函数 —— 从组内所有行计算出一个单一值
  • HAVING —— 过滤分组(类似 WHERE,但作用于分组结果)
  • SELECT 中所有不在聚合函数里的列,都必须出现在 GROUP BY 中

SQL 执行顺序

SQL 子句的书写顺序与执行顺序不同。理解这一点有助于解释为什么 HAVING 可以引用聚合函数而 WHERE 不行,以及为什么不能在 HAVING 子句中使用 SELECT 的别名。

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
步骤 子句 发生了什么
1 FROM 读取表并计算所有 JOIN
2 WHERE 过滤单行 —— 这里不允许使用聚合函数
3 GROUP BY 将剩余行折叠为分组
4 HAVING 过滤分组 —— 这里允许使用聚合函数
5 SELECT 计算输出列和别名
6 ORDER BY 对最终结果集排序

因此,WHERE status = 'delivered' 在分组之前执行(快速的行级过滤),而 HAVING COUNT(*) > 2 在分组之后执行(过滤整个分组)。

COUNT()

COUNT(*) 统计组内所有行数。COUNT(column) 只统计该列中非 NULL 的值。

示例:每个部门的员工数量

示例:每个客户的订单数量

示例:已送达与其他状态的数量对比

使用带 CASE 表达式的 COUNT 只统计满足条件的订单:

SUM()

SUM(column) 对组内某个数值列的所有值求和。

示例:每个部门的工资总额

示例:每个品类的总收入

将收入计算为 quantity * unit_price

示例:按客户和品类拆分收入

按多个列分组,以进一步细分收入:

AVG()

AVG(column) 返回组内所有非 NULL 值的算术平均数。

示例:每个部门的平均工资

示例:每个客户的平均订单金额

MIN() 和 MAX()

MIN(column)MAX(column) 分别返回组内最小值和最大值。

示例:每个部门的工资区间

示例:每个客户的首单和末单日期

组合多个聚合函数

你可以在一条查询中组合任意数量的聚合函数。

示例:完整的部门汇总

示例:每个客户的订单汇总

HAVING

HAVING 在聚合之后过滤分组 —— 它是针对分组结果的 WHERE 等价物。

示例:员工数大于 2 的部门

示例:总收入超过 2000 的客户

示例:平均订单金额低于 500 的品类

WHERE 与 HAVING 的区别

WHERE 在分组之前过滤单行。HAVING 在聚合之后过滤分组。

示例:每个客户仅统计已送达订单的收入

使用 WHERE 在分组前排除行,再用 HAVING 过滤结果:

带 JOIN 的 GROUP BY

你可以先对多张表做连接,然后对连接后的数据进行分组。

示例:通过员工表关联统计每个部门的收入

示例:按品类和月份统计订单数量与收入

带表达式的 GROUP BY

你可以按计算表达式分组,而不仅仅是按原始列分组。

示例:每年入职的员工数量

示例:按月份统计订单

COUNT(DISTINCT)

COUNT(DISTINCT column) 统计组内唯一值的数量。

示例:每个客户订购的不同产品数量

使用多条件过滤分组

HAVING 支持任意布尔表达式,包括 AND/OR 组合。

示例:高价值的活跃客户

查找订单数大于 1 且总收入超过 1000 的客户:

实战示例:销售报表

把前面内容组合起来,构建一个完整的销售汇总报表。

示例:品类表现报表

示例:按收入排序的畅销产品

GROUP BY 与窗口函数

GROUP BY 会把多行折叠为每组一行。窗口函数则保留所有行,只是额外添加一个计算列 —— 详情见 SQL 窗口函数教程

示例:GROUP BY 每个部门只返回一行

示例:窗口函数保留所有行

GROUP BY 的常见用例

  • 报表仪表盘 —— 按类别或时间段汇总总数、平均值和计数
  • 人力/数量分析 —— 按分组统计员工、用户或记录数量
  • 收入拆分 —— 按产品、区域或客户统计总收入和平均收入
  • 趋势分析 —— 按月份或年份分组,发现时间序列模式
  • 每组 Top-N —— 结合子查询或 CTE 找出每个分组中的最佳表现者
  • 数据校验 —— 按分组统计重复值或 NULL 值
  • 分群分析(Cohort) —— 按注册日期或行为分群用户

性能优化建议

  1. 为 GROUP BY 列建立索引 —— 对分组列建立索引可以加速排序和分组步骤
  2. 用 WHERE 尽早过滤 —— 在分组前用 WHERE 减少行数,比事后用 HAVING 过滤更快
  3. 避免 SELECT * —— 只选择真正需要的列;聚合操作在数据更窄时性能更好
  4. 使用覆盖索引 —— 同时包含 GROUP BY 列和聚合列的索引可以避免全表扫描
  5. 考虑物化视图 —— 对于大表上频繁计算的聚合,一些数据库支持预计算汇总结果

小结

  • GROUP BY 会把具有相同列值的多行折叠为每种唯一组合一行的汇总结果
  • COUNT(*) 统计所有行;COUNT(column) 统计非 NULL 值;COUNT(DISTINCT column) 统计唯一值数量
  • SUM 对数值求和;AVG 计算平均值;MIN / MAX 找出边界值
  • SELECT 中的每一列要么出现在 GROUP BY 中,要么被聚合函数包裹
  • WHERE 在分组前过滤行;HAVING 在聚合后过滤分组
  • 你可以按表达式 GROUP BY(例如用 SUBSTR(date, 1, 7) 做按月分组)
  • 先 JOIN 再 GROUP BY,可以把多数据源合并到一个报表中
  • 当你需要在不丢失明细行的情况下获得分组级指标时,用窗口函数而不是 GROUP BY

试着把 GROUP BY 与 JOIN、CASE 表达式和 HAVING 子句结合起来,构建满足你数据分析需求的各种报表!