跳到主要内容

GROUPING SETS 子句

GROUPING SETSROLLUPCUBE 可用于 GROUP BY 子句,以便在同一条查询中按多个维度进行分组。 请注意,该语法与 GROUP BY ALL 不兼容。

示例

按给定的四个不同维度计算平均收入:

-- the syntax () denotes the empty set (i.e., computing an ungrouped aggregate)
SELECT city, street_name, avg(income)
FROM addresses
GROUP BY GROUPING SETS ((city, street_name), (city), (street_name), ());

按相同维度计算平均收入:

SELECT city, street_name, avg(income)
FROM addresses
GROUP BY CUBE (city, street_name);

(city, street_name)(city)() 这些维度计算平均收入:

SELECT city, street_name, avg(income)
FROM addresses
GROUP BY ROLLUP (city, street_name);

说明

GROUPING SETS 可在单条查询中跨不同的 GROUP BY 子句执行同一种聚合。

CREATE TABLE students (course VARCHAR, type VARCHAR);
INSERT INTO students (course, type)
VALUES
('CS', 'Bachelor'), ('CS', 'Bachelor'), ('CS', 'PhD'), ('Math', 'Masters'),
('CS', NULL), ('CS', NULL), ('Math', NULL);
SELECT course, type, count(*)
FROM students
GROUP BY GROUPING SETS ((course, type), course, type, ());
coursetypecount_star()
MathNULL1
NULLNULL7
CSPhD1
CSBachelor2
MathMasters1
CSNULL2
MathNULL2
CSNULL5
NULLNULL3
NULLMasters1
NULLBachelor2
NULLPhD1

在上述查询中,我们跨四个不同集合分组:course, typecoursetype()(空分组)。对于某条结果所对应分组集中不存在的列,结果会显示 NULL。也就是说,上述查询等价于下面这些 UNION ALL 子句:

-- Group by course, type:
SELECT course, type, count(*)
FROM students
GROUP BY course, type
UNION ALL
-- Group by type:
SELECT NULL AS course, type, count(*)
FROM students
GROUP BY type
UNION ALL
-- Group by course:
SELECT course, NULL AS type, count(*)
FROM students
GROUP BY course
UNION ALL
-- Group by nothing:
SELECT NULL AS course, NULL AS type, count(*)
FROM students;

CUBEROLLUP 是语法糖,可方便生成常见的分组集合。

ROLLUP 子句会生成某个分组集合的所有“子分组”。例如,ROLLUP (country, city, zip) 会生成 (country, city, zip), (country, city), (country), ()。这对产出不同明细层级的分组结果很有用。其会生成 n+1 个分组集合,其中 nROLLUP 子句中的项数。

CUBE 会为输入项的所有组合生成分组集合。例如,CUBE (country, city, zip) 会生成 (country, city, zip), (country, city), (country, zip), (city, zip), (country), (city), (zip), ()。其会生成 2^n 个分组集合。

使用 GROUPING_ID() 识别分组集合

GROUPING SETSROLLUPCUBE 生成的超级聚合行,通常可以通过相应分组列返回的 NULL 值来识别。但如果分组列本身就可能包含真实的 NULL,就很难判断结果集里的 NULL 到底来自原始数据,还是由分组结构生成。GROUPING_ID()GROUPING() 函数就是为识别哪些分组生成了这些超级聚合行而设计的。

GROUPING_ID() 是一个聚合函数,接收组成分组的列表达式,并返回一个 BIGINT 值。对于非超级聚合行,返回值为 0。对于超级聚合行,会返回一个整数,用于标识生成该超级聚合的表达式组合。下面的示例更直观:

WITH days AS (
SELECT
year("generate_series") AS y,
quarter("generate_series") AS q,
month("generate_series") AS m
FROM generate_series(DATE '2023-01-01', DATE '2023-12-31', INTERVAL 1 DAY)
)
SELECT y, q, m, GROUPING_ID(y, q, m) AS "grouping_id()"
FROM days
GROUP BY GROUPING SETS (
(y, q, m),
(y, q),
(y),
()
)
ORDER BY y, q, m;

结果如下:

yqmgrouping_id()
2023110
2023120
2023130
20231NULL1
2023240
2023250
2023260
20232NULL1
2023370
2023380
2023390
20233NULL1
20234100
20234110
20234120
20234NULL1
2023NULLNULL3
NULLNULLNULL7

在这个示例中,最低分组层级是月份层级,即分组集 (y, q, m)。对应这一级别的结果行只是普通聚合行,因此 GROUPING_ID(y, q, m) 返回 0。分组集 (y, q) 会在月份层级之上产生超级聚合行,此时 m 列为 NULLGROUPING_ID(y, q, m) 返回 1。分组集 (y) 会在季度层级之上产生超级聚合行,此时 mq 列为 NULLGROUPING_ID(y, q, m) 返回 3。最后,分组集 () 会为整个结果集产生一条超级聚合行,yqm 都为 NULL,此时 GROUPING_ID(y, q, m) 返回 7

要理解返回值与分组集之间的关系,可以把 GROUPING_ID(y, q, m) 看作在写一个位字段(bitfield):第一位对应传给 GROUPING_ID() 的最后一个表达式,第二位对应倒数第二个表达式,依此类推。将 GROUPING_ID() 转为 BIT 会更清楚:

WITH days AS (
SELECT
year("generate_series") AS y,
quarter("generate_series") AS q,
month("generate_series") AS m
FROM generate_series(DATE '2023-01-01', DATE '2023-12-31', INTERVAL 1 DAY)
)
SELECT
y, q, m,
GROUPING_ID(y, q, m) AS "grouping_id(y, q, m)",
right(GROUPING_ID(y, q, m)::BIT::VARCHAR, 3) AS "y_q_m_bits"
FROM days
GROUP BY GROUPING SETS (
(y, q, m),
(y, q),
(y),
()
)
ORDER BY y, q, m;

其返回结果如下:

yqmgrouping_id(y, q, m)y_q_m_bits
2023110000
2023120000
2023130000
20231NULL1001
2023240000
2023250000
2023260000
20232NULL1001
2023370000
2023380000
2023390000
20233NULL1001
20234100000
20234110000
20234120000
20234NULL1001
2023NULLNULL3011
NULLNULLNULL7111

请注意,传给 GROUPING_ID() 的表达式数量及其顺序,与 GROUPING SETS 子句中实际定义的分组(或 ROLLUPCUBE 隐含的分组)是相互独立的。只要传入 GROUPING_ID() 的表达式出现在 GROUPING SETS 子句中的任意位置,当某个表达式被汇总为超级聚合时,GROUPING_ID() 就会设置与该表达式位置对应的位。

语法