在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

GROUP BY 与聚合

GROUP BY 按完整分组键元组划分输入行,为每组维护聚合,再将每组缩成一行结果。增加键列,可以把已有组细分。当前查询中每个源行恰好属于一组,每组恰好输出一行,这两条规则决定数值与结果粒度。

COUNT(*) 数全部行,COUNT(column)、SUM(column)、AVG(column) 忽略该列的 NULL。NULL 出现时,行数与非空值数便不再相同。

GROUP BY 如何工作

例如按地区统计订单与金额:

SELECT
  region,
  COUNT(*)       AS orders,
  COUNT(amount)  AS recorded_amounts,
  SUM(amount)    AS revenue,
  AVG(amount)    AS average_amount
FROM sales
GROUP BY region;

当前键为 (region);若改成 region、channel,完整键就为二元组,North / online 与 North / store 必须分开。

COUNT(*) 接收组内每行;COUNT(amount) 只计非 NULL 金额;SUM(amount) 加非 NULL 值,全空则为 NULL;AVG(amount) 用非空值总和除以 COUNT(amount),不是 COUNT(*)。四个聚合产生每组一行中的四列,不是四行。

GROUP BY 逐步演示

默认七行按下列顺序输入,键为 region。

orderregionchannelamount
o1Northonline120
o2Southstore90
o3Northstore80
o4Westonline40
o5Southonline60
o6NorthonlineNULL
o7Weststore110
  1. o1 创建 North,COUNT(*)=1、COUNT(amount)=1、SUM=120。
  2. o2 创建 South,两计数为 1,SUM=90。
  3. o3 虽不邻接 o1,键仍是 North,更新为两计数 2、SUM=200。
  4. o4 创建 West,两计数 1、SUM=40。
  5. o5 回到 South,两计数 2、SUM=150。
  6. o6 金额 NULL,North 的 COUNT(*) 从 2 到 3,COUNT(amount) 仍为 2,SUM 仍为 200。
  7. o7 更新 West,两计数 2、SUM=150。

结束后 North 的平均为 200 / 2 = 100,不是除以 3;South、West 都为 150 / 2 = 75。各组输出一行:

regionCOUNT(*)COUNT(amount)SUM(amount)AVG(amount)
North32200100
South2215075
West2215075

显示次序是讲解顺序,SQL 需要 ORDER BY 才保证排序。画面左侧保留源表,中间把当前行送入组,右侧写结果。前三行说明机制后加快重复分配,NULL 更新单独放慢,以区分两种计数。

分组粒度怎样改变结果

分组键组数
聚合但无 GROUP BY全局一个组1
GROUP BY region(region)3
GROUP BY region, channel(region, channel)6

加 channel 只能细分地区组,不能把原本不同地区合到一起,因为完整键仍含 region。成员变化如下:

地区原组完整新键成员
North:o1、o3、o6(North, online)o1、o6
North:o1、o3、o6(North, store)o3
South:o2、o5(South, store)o2
South:o2、o5(South, online)o5
West:o4、o7(West, online)o4
West:o4、o7(West, store)o7

North 从一个三行组变成两组,子组成员仍合为 o1、o3、o6,没有丢失或重复。其他地区同理,解释了 3→6,计数栏归纳为 1→3→6。

地区粒度每行表示一个地区,地区加渠道粒度每行表示一对值。下游若把地区总数连接到多个渠道行后再求和,会重复计入;必须明确匹配粒度与再聚合规则。

聚合查询无 GROUP BY 时,整个输入形成一个全局组,返回一行且无分组键列,不是返回原始行。

SQL 规定逻辑结果,不指定物理算法。数据库可哈希键、排序、利用索引、流式处理有序输入或并行合并部分聚合,时间和内存不同,逻辑分组相同。使用前应明确一结果行代表什么、完整键是否表达该粒度、NULL 与分母规则、下游连接是否保持对应粒度。

边界情况

  • 两行金额全 NULL:COUNT(*)=2、COUNT(amount)=0、SUM=NULL、AVG=NULL。
  • 分组键 NULL 可归为同一组,组合键仍比较完整元组。这与等值连接中 NULL = NULL 不为 TRUE 不同。
  • 空输入带 GROUP BY region:没有组,返回零行。
  • 空输入的无 GROUP BY 全局聚合:返回一行,两计数 0,SUM 和 AVG 为 NULL。
  • 每行键唯一:每组只有一行,结果行数不减少。

常见错误

  • 用 SUM(amount)/COUNT(*) 代替 AVG,遇到 NULL 就错。
  • GROUP BY region 却直接选择 channel。一个地区可能有两渠道,没有单一组值;应聚合或放入键。标准允许某些函数依赖投影,方言行为也有差别,需明确依赖。
  • 按源行相邻关系分组,同键非相邻也属于同组。
  • 每个 NULL 行单独开组,相同完整分组键应归在一起。
  • 没有 ORDER BY 却依赖一次运行的稳定顺序。
  • 看见画布桶就断言物理计划是哈希聚合,桶只表示成员关系。

模型范围

本课展示逻辑分组、聚合 NULL 行为、粒度和行数。省略排序规则、精度溢出、DISTINCT 与有序集聚合、HAVING、分组集、函数依赖规则、优化器、索引、并行部分聚合、事务可见性、方言行为。实际查询需按数据库的 SQL 与执行计划规则核对。