GROUP BY子句

更新时间:
复制 MD 格式

GROUP BY 子句通常与聚合函数一起使用,按照一列或多列的值对查询结果中的数据行分组,为每个不同的组合生成一个汇总行。除显式指定分组键外,还可以使用 GROUP BY ALL 根据 SELECT 列表自动确定分组键,或搭配 ROLLUP、CUBE 和 GROUPING SETS 子句扩展分组功能。

基本语法

可根据分析场景选择以下分组写法,其中 ROLLUP、CUBE、GROUPING SETS 在 GROUP BY 基础上扩展分组功能:

分析场景

适用写法

按显式指定的列或表达式对分析结果分组(最常用)

GROUP BY

分组键较多或需要调整分析维度,由 SELECT 列表自动确定分组键

GROUP BY ALL

为每个分组返回一个小计,为所有分组返回一个总计

GROUP BY ROLLUP

按照所有可能的列组合分组

GROUP BY CUBE

按照指定的多个分组集合分别分组

GROUP BY GROUPING SETS

重要

在 SQL 语句中,普通分组查询的 SELECT 列表只能包含以下内容:

  • GROUP BY 子句中指定的列或表达式

  • 对任意列的聚合计算结果,如 COUNT()、SUM() 等

  • 使用 GROUP BY ALL 时,分组键由 SELECT 列表自动推导,无需在 GROUP BY 子句中重复列出。

  • 还可以在分组结果上使用窗口函数,参见示例 9。

直接选择非 GROUP BY 列是不允许的,因为这些列的值在分组后可能不唯一,无法明确其含义。例如,以下语句是非法的:

* | SELECT status, request_time, COUNT(*) AS PV GROUP BY status

原因是 request_time 并未包含在 GROUP BY 子句中,也未聚合。正确的写法可以是:

* | SELECT status, arbitrary(request_time), COUNT(*) AS PV GROUP BY status

arbitrary(request_time) 是对 request_time 聚合的一种方式,表示从每个分组中任意选取一个 request_time 值,既符合 SQL 语法规则,又能满足查询需求。

GROUP BY

对分析结果分组。

SELECT
 key1,
 ...,
 aggregate_function
GROUP BY key,...

参数说明:

  • key1:分组键,即用于分组的列,支持按日志字段名称或聚合函数计算结果列分组。GROUP BY 子句支持单列或多列。

  • aggregate_function:应用在每个分组上的聚合函数,如 count、min、max、avg 和 sum 等。

完整示例参见示例 1~示例 3。

GROUP BY ALL

GROUP BY ALL 自动将 SELECT 列表中不包含聚合函数、也不包含窗口函数的表达式作为分组键,无需在 GROUP BY 子句中重复列出这些表达式。适合分组键较多,或需要调整分析维度的场景。

SELECT
 key1,
 key2,
 aggregate_function
GROUP BY ALL

参数说明:

  • key1、key2:分组键,可以是字段或表达式,例如 status、request_method、substr(request_method, 1, 1) 或 CASE 表达式。表达式按其完整计算结果分组;GROUP BY ALL 后不跟任何字段或别名,SELECT 列表中已设置的别名无需重复引用。

  • aggregate_function:应用在每个分组上的聚合函数,例如 count(*)、sum(request_time) 和 avg(request_time)。

例如,以下两条语句等价:

* | SELECT request_method, status, count(*) AS PV GROUP BY ALL
* | SELECT request_method, status, count(*) AS PV GROUP BY request_method, status

SELECT 列表的通用合法性规则参见本章开头的重要提示,GROUP BY ALL 的使用规则和限制如下:

  • 自动分组以当前 SELECT 列表为准,不会按日志中的全部字段分组。增加或删除非聚合、非窗口表达式会相应改变分组维度。

  • 不支持在同一层查询中将 GROUP BY ALL 与 SELECT *、SELECT 表别名.* 或 SELECT *, count(*) 组合使用,请显式列出字段。该限制针对 SELECT * 这类通配选择,显式列出字段后使用 count(*) 等聚合函数不受影响。

  • 包含聚合函数或窗口函数的整个表达式不会被自动加入分组键,例如 sum(request_time) / count(*) 整体不参与分组。如果此类表达式之外还直接选择了未聚合的字段(例如未经 arbitrary() 等聚合函数处理的 request_time),仍须满足普通 GROUP BY 的分组规则。

  • 当 SELECT 列表只有聚合表达式时,对全部符合查询条件的数据全局聚合。例如 * | SELECT count(*) AS PV, sum(request_time) AS total_time GROUP BY ALL 与省略 GROUP BY ALL 的写法等价。

  • 支持与 WHERE、HAVING、ORDER BY 以及窗口函数配合使用。窗口函数基于分组聚合后的结果计算,其引用的字段仍需满足分组规则。

  • 只有单独书写的 GROUP BY ALL 才会自动推导分组键,GROUP BY ALL status 或 GROUP BY ALL ROLLUP (...) 中的 ALL 不会触发自动推导。

  • 需要按名为 all 的字段或别名分组时,请使用双引号,例如 GROUP BY "all"。默认情况下,即使存在名为 all 的字段,单独的 GROUP BY ALL 仍会自动推导分组键。

完整示例参见示例 7~示例 9。

GROUP BY ROLLUP

GROUP BY ROLLUP 子句按汇总层级分组,为每个分组返回一个小计,为所有分组返回一个总计。例如 GROUP BY ROLLUP (a, b) 的结果集为 (a, b)、(a, null) 和 (null, null)。完整示例参见示例 6。

SELECT
 key1,
 ...,
 aggregate_function
GROUP BY ROLLUP (key1,...)

参数说明如下所示:

  • key1:是您希望根据其值来分组的列,支持按照日志字段名称或聚合函数计算结果列进行分组。GROUP BY子句支持单列或多列。

  • aggregate_function:是应用在每个分组上的聚合函数,如count、min、max、avg和sum等。

GROUP BY CUBE

GROUP BY CUBE 子句按照所有可能的列组合分组。例如 GROUP BY CUBE (a, b) 的结果集为 (a, b)、(null, b)、(a, null) 和 (null, null)。完整示例参见示例 5。

SELECT
 key1,
 ...,
 aggregate_function
GROUP BY CUBE (key1,...)

参数说明如下所示:

  • key1:是您希望根据其值来分组的列,支持按照日志字段名称或聚合函数计算结果列进行分组。GROUP BY子句支持单列或多列。

  • aggregate_function:是应用在每个分组上的聚合函数,如count、min、max、avg和sum等。

GROUP BY GROUPING SETS

GROUP BY GROUPING SETS 子句按照指定的多个分组集合分别分组,未参与某个分组集合的列在该集合的结果中为 null。例如 GROUP BY GROUPING SETS (a, b) 的结果集为 (a, null) 和 (null, b)。完整示例参见示例 4。

SELECT
 key1,
 ...,
 aggregate_function
GROUP BY GROUPING SETS (key1,...)

参数说明如下所示:

  • key1:是您希望根据其值来分组的列,支持按照日志字段名称或聚合函数计算结果列进行分组。GROUP BY子句支持单列或多列。

  • aggregate_function:是应用在每个分组上的聚合函数,如count、min、max、avg和sum等。

示例

示例 1:按状态码统计请求次数

统计不同状态码对应的请求次数。

  • 查询和分析语句

* | SELECT status, count(*) AS PV GROUP BY status
  • 查询和分析结果以表格形式展示,包含 status 和 PV 两列,分别显示各 HTTP 状态码及其对应的页面访问量统计值。

示例 2:按小时统计网站访问 PV

按照每小时的时间粒度计算网站访问 PV。

  • 查询和分析语句

    __time__ 字段为日志服务中的保留字段,表示时间列;time 为 date_trunc('hour', __time__) 的别名,函数用法参见 date_trunc 函数。

* |
SELECT
 count(*) AS PV,
 date_trunc('hour', __time__) AS time
GROUP BY
 time
ORDER BY
 time
LIMIT
 1000
  • 查询和分析结果:返回包含 PV 和 time 两列的结果,示例数据:PV 分别为 1202、10159、28001,对应时间为 2021-08-10 00:00:00.000、2021-08-10 01:00:00.000、2021-08-10 02:00:00.000。

示例 3:按 5 分钟时间粒度统计 PV

按照每 5 分钟的时间粒度计算 PV。

  • 查询和分析语句

由于 date_trunc 函数只能按照固定时间间隔统计,需要按照自定义时间统计分析时,请按照数学取模方法分组。例如 %300 表示按照 5 分钟的时间粒度取模对齐。

* |
SELECT
 count(*) AS PV,
 __time__-__time__ % 300 AS time
GROUP BY
 time
LIMIT
 1000
  • 查询和分析结果返回三条记录:PV=143 对应 time=1628525100,PV=31 对应 time=1628526600,PV=44 对应 time=1628526900。

示例 4:按指定分组集合统计访问数据 (GROUPING SETS)

根据请求方法和请求状态分组,先计算各个请求方法对应的访问数据,再计算各个请求状态对应的访问数量。

  • 查询和分析语句

* |
SELECT
 request_method,
 status,
 count(*) AS PV
GROUP BY
 GROUPING SETS (request_method, status)
  • 查询和分析结果:按 request_method 分组时,status 为 null,各方法 PV 值分别为:GET=285、POST=51、DELETE=28、PUT=60;按 status 分组时,request_method 为 null,各状态码 PV 值分别为:200=382、204=2、501=2、202=4、305=4。

示例 5:按所有列组合统计访问数据 (CUBE)

根据请求方法和请求状态分组,分组集包括 (null, null)、(request_method, null)、(null, status) 和 (request_method, status),计算以上各个分组的访问数量。

  • 查询和分析语句

* |
SELECT
 request_method,
 status,
 count(*) AS PV
GROUP BY
 CUBE (request_method, status)
  • 查询和分析结果:查询结果包含 request_method、status、PV 三列,共 43 条记录。CUBE 分组产生的聚合行中,request_method 或 status 为 null 表示该维度的小计。例如:null/null 的 PV 为 285(总计),GET/null 为 189,POST/null 为 47,PUT/null 为 39,DELETE/null 为 9,HEAD/null 为 1;null/200 为 259,null/201 为 2,null/202 为 2。

示例 6:按汇总层级统计小计与总计 (ROLLUP)

根据请求方法和请求状态分组,分组集包括 (request_method, status)、(request_method, null) 和 (null, null),计算以上各个分组的访问数量。

  • 查询和分析语句

* |
SELECT
 request_method,
 status,
 count(*) AS PV
GROUP BY
 ROLLUP (request_method, status)
  • 查询和分析结果:查询结果包含 request_method、status、PV 三列,共 33 条记录。ROLLUP 函数在各分组层级生成汇总行,其中 null 表示该列已被聚合:例如 GET / null / 162 表示 GET 方法所有状态码的合计 PV 为 162,null / null / 285 表示所有方法与状态码的总计 PV 为 285。

示例 7:自动按多个字段分组 (GROUP BY ALL)

根据请求方法和状态码统计请求次数,只保留请求次数大于 100 的分组。

  • 查询和分析语句

* |
SELECT
 request_method,
 status,
 count(*) AS PV
GROUP BY ALL
HAVING count(*) > 100
ORDER BY PV DESC
  • 查询和分析结果:包含 request_method、status 和 PV 三列,仅返回请求次数大于 100 的组合,并按 PV 从高到低排列。分组方式等价于 GROUP BY request_method, status。

示例 8:自动按表达式分组 (GROUP BY ALL)

按照每 5 分钟的时间粒度和请求方法统计 PV,无需重复填写时间表达式或其别名。

  • 查询和分析语句

* |
SELECT
 __time__ - __time__ % 300 AS time,
 request_method,
 count(*) AS PV
GROUP BY ALL
ORDER BY time, request_method
LIMIT 1000
  • 查询和分析结果:包含 time、request_method 和 PV 三列。分组方式等价于 GROUP BY __time__ - __time__ % 300, request_method。

示例 9:对分组结果计算排名 (GROUP BY ALL)

统计各状态码的请求次数,再按照请求次数从高到低计算排名。

  • 查询和分析语句

* |
SELECT
 status,
 count(*) AS PV,
 rank() OVER (ORDER BY count(*) DESC) AS ranking
GROUP BY ALL
ORDER BY ranking, status
  • 查询和分析结果:包含 status、PV 和 ranking 三列。仅 status 作为分组键,count(*) 用于聚合,rank() 对聚合结果排名,不参与分组;请求次数相同的状态码具有相同排名。