GROUP BY 子句会将 SELECT 查询切换为聚合模式,其工作方式如下:
GROUP BY子句包含一个表达式列表 (或单个表达式,此时视为长度为 1 的列表) 。该列表充当“分组键”,其中每个单独的表达式称为“键表达式”。- SELECT、HAVING 和 ORDER BY 子句中的所有表达式都必须基于键表达式进行计算,或基于作用于非键表达式 (包括普通列) 的聚合函数进行计算。换句话说,从表中选出的每一列,要么用于键表达式,要么位于聚合函数内部,但不能同时用于两者。
- 聚合
SELECT查询的结果中包含的行数,将等于源表中“分组键”唯一值的数量。通常,这会显著减少行数,往往会减少几个数量级,但并非总是如此:如果所有“分组键”值都互不相同,行数将保持不变。
还有一种对表执行聚合的方式。如果查询只在聚合函数内部使用表列,则可以省略
GROUP BY 子句,此时会假定按空键集进行聚合。这类查询始终只返回一行。NULL 的处理
NULL==NULL。这与大多数其他上下文中对 NULL 的处理方式不同。
下面的示例说明了这意味着什么。
假设你有这样一张表:
SELECT sum(x), y FROM t_null_big GROUP BY y 的结果如下:
y = NULL,GROUP BY 会对 x 求和,就好像 NULL 是一个实际的值一样。
如果向 GROUP BY 传递多个键,结果会返回所选项的所有组合,就好像 NULL 是一个特定的值一样。
ROLLUP 修饰符
ROLLUP 修饰符用于根据 GROUP BY 列表中键表达式的顺序计算小计。小计行会附加在结果表之后。
小计按相反的顺序计算:先为列表中的最后一个键表达式计算小计,再为前一个计算,依此类推,直到第一个键表达式。
在小计行中,已“grouped”的键表达式的值会被设为 0 或空字符串。
请注意,HAVING 子句可能会影响小计结果。
Query
GROUP BY 部分包含三个键表达式,因此结果中会有四个表,小计按从右到左的顺序逐级“汇总”:
GROUP BY year, month, day;GROUP BY year, month(此时day列补零) ;GROUP BY year(此时month和day列都补零) ;- 以及总计 (此时三个键表达式列都为零) 。
Response
WITH 关键字来写。
Query
- group_by_use_nulls 设置,用于兼容 SQL 标准。
CUBE 修饰符
CUBE 修饰符用于计算 GROUP BY 列表中键表达式各种组合的小计。小计行会添加在结果表之后。
在小计行中,所有“已分组”键表达式的值都将被设为 0 或空字符串。
请注意,HAVING 子句可能会影响小计结果。
Query
GROUP BY 部分包含三个键表达式,因此结果中会有八个表,分别对应所有键表达式组合的小计:
GROUP BY year, month, dayGROUP BY year, monthGROUP BY year, dayGROUP BY yearGROUP BY month, dayGROUP BY monthGROUP BY day- 以及总计。
GROUP BY 中的列会以零填充。
Response
WITH 关键字来写。
Query
- 用于兼容 SQL 标准的 group_by_use_nulls 设置项。
WITH TOTALS 修饰符
WITH TOTALS 修饰符,则会额外计算出一行。该行的键列包含默认值 (零或空字符串) ,聚合函数列则包含基于所有行计算出的值 (即“总计”值) 。
这个额外的行仅会在 JSON*、TabSeparated* 和 Pretty* 格式中生成,并与其他行分开输出:
- 在
XML和JSON*格式中,这一行会作为单独的totals字段输出。 - 在
TabSeparated*、CSV*和Vertical格式中,这一行位于主结果之后,前面有一个空行 (也就是在其他数据之后) 。 - 在
Pretty*格式中,这一行会在主结果后作为单独的表输出。 - 在
Template格式中,这一行会按照指定模板输出。 - 在其他格式中不可用。
totals 会在
SELECT 查询结果中输出,但不会在 INSERT INTO ... SELECT 中输出。WITH TOTALS 的执行方式可能不同。其行为取决于 totals_mode 设置。
配置 totals 处理
totals_mode = 'before_having'。在这种情况下,totals 会基于所有行进行计算,包括未通过 HAVING 和 max_rows_to_group_by 的行。
其他可选值只会将通过 HAVING 的行计入 totals,并且在设置 max_rows_to_group_by 和 group_by_overflow_mode = 'any' 时行为有所不同。
after_having_exclusive – 不包含未通过 max_rows_to_group_by 的行。换句话说,如果省略 max_rows_to_group_by,totals 的行数将小于或等于其原本的行数。
after_having_inclusive – 将所有未通过 max_rows_to_group_by 的行都包含在 totals 中。换句话说,如果省略 max_rows_to_group_by,totals 的行数将大于或等于其原本的行数。
after_having_auto – 统计通过 HAVING 的行数。如果该数量超过某个阈值 (默认为 50%) ,则将所有未通过 max_rows_to_group_by 的行都包含在 totals 中;否则不包含这些行。
totals_auto_threshold – 默认为 0.5,即 after_having_auto 的系数。
如果未使用 max_rows_to_group_by 和 group_by_overflow_mode = 'any',则所有 after_having 变体都相同,可以使用其中任意一个 (例如 after_having_auto) 。
你可以在子查询中使用 WITH TOTALS,包括 JOIN 子句中的子查询 (在这种情况下,相应的总计值会合并) 。
GROUP BY ALL
GROUP BY ALL 等同于列出 SELECT 中所有非聚合函数的表达式。
例如:
GROUP BY 键将包含我们能从中提取出的尽可能多的非聚合字段。
例如:
示例
GROUP BY 都会计算出一组聚合函数值。
GROUPING SETS 修饰符
GROUPING SETS 来表示。
尽管带有 ROLLUP、CUBE 和 GROUPING SETS 修饰符的查询在语法上等价,但它们的执行方式可能不同。
GROUPING SETS 会尝试并行执行所有操作,而 ROLLUP 和 CUBE 则会在单个线程中完成聚合结果的最终合并。
当源列包含默认值时,可能很难区分某一行是否属于以这些列作为键的聚合结果。
为了解决这个问题,必须使用 GROUPING 函数。
示例
以下两个查询是等价的。
- 用于兼容 SQL 标准的 group_by_use_nulls 设置。
实现细节
取决于表排序键的 GROUP BY 优化
GROUP BY 表达式至少包含排序键的前缀或单射函数,则可以更高效地进行聚合。在这种情况下,当从表中读取到新键时,聚合的中间结果即可完成最终计算并发送给客户端。此行为由 optimize_aggregation_in_order 设置启用。这种优化可以减少聚合期间的内存使用量,但在某些情况下也可能会降低查询执行速度。
外部内存中的 GROUP BY
GROUP BY 期间的内存使用量。
max_bytes_before_external_group_by 设置决定了将 GROUP BY 临时数据转储到文件系统时的 RAM 使用阈值。如果设为 0 (默认值) ,则表示禁用。
或者,您也可以设置 max_bytes_ratio_before_external_group_by,这样只有当查询使用的内存达到一定阈值时,才允许 GROUP BY 使用外部内存。
使用 max_bytes_before_external_group_by 时,我们建议将 max_memory_usage 设为其大约两倍 (或者将 max_bytes_ratio_before_external_group_by 设为 0.5) 。这是因为聚合分为两个阶段:读取数据并生成中间数据 (1) ,以及合并中间数据 (2) 。只有在阶段 1 才会将数据转储到文件系统。如果临时数据没有被转储,那么阶段 2 可能需要最多与阶段 1 相同数量的内存。
例如,如果 max_memory_usage 设置为 10000000000,并且您希望使用外部聚合,那么将 max_bytes_before_external_group_by 设为 10000000000、将 max_memory_usage 设为 20000000000 是合理的。触发外部聚合时 (如果至少发生过一次临时数据转储) ,RAM 的最大消耗只会比 max_bytes_before_external_group_by 略高一点。
对于分布式查询处理,外部聚合会在远程服务器上执行。为了让发起请求的服务器只使用少量 RAM,请将 distributed_aggregation_memory_efficient 设为 1。
在合并已写入磁盘的数据时,以及在启用 distributed_aggregation_memory_efficient 设置后合并来自远程服务器的结果时,最多会消耗总 RAM 的 1/256 * the_number_of_threads。
启用外部聚合后,如果数据量小于 max_bytes_before_external_group_by (即数据未写入磁盘) ,则查询运行速度与未启用外部聚合时一样快。如果有任何临时数据被写入磁盘,运行时间将延长数倍 (大约三倍) 。
如果您在 GROUP BY 之后使用带有 LIMIT 的 ORDER BY,那么 RAM 使用量取决于 LIMIT 中的数据量,而不是整张表的数据量。但如果 ORDER BY 没有 LIMIT,不要忘记启用外部排序 (max_bytes_before_external_sort) 。