GROUPING SET, ROLLUP 与 CUBE
在数据分析与报表生成(OLAP)场景中,我们经常需要对数据进行多维度、多层次的分组聚合计算。ProtonBase 提供了强大的 GROUP BY
扩展功能:RROUPING SET
、ROLLUP
和 CUBE
,能够高效地生成小计、总计和多维交叉报表,极大地简化了复杂聚合查询的编写。
基本概念与前置知识
在深入了解这三个功能之前,请确保你已理解基本的 GROUP BY
和聚合函数(如 SUM()
, COUNT()
, AVG()
)的使用。
示例表 sales
:
为了后续说明,我们假设有一个销售记录表 sales
。
CREATE TABLE sales (
region VARCHAR(50),
country VARCHAR(50),
product_category VARCHAR(50),
sales_amount NUMERIC
);
-- 示例数据
INSERT INTO sales VALUES
('Asia', 'China', 'Electronics', 10000),
('Asia', 'China', 'Clothing', 5000),
('Asia', 'Japan', 'Electronics', 8000),
('Asia', 'Japan', 'Clothing', 3000),
('Europe', 'UK', 'Electronics', 7000),
('Europe', 'UK', 'Clothing', 2000),
('Europe', 'Germany', 'Electronics', 9000),
('Europe', 'Germany', 'Clothing', 4000);
GROUPING SETS:自定义分组集
GROUPING SETS
是最基础也是最灵活的原语。它允许你指定多个你希望同时计算的分组组合。ROLLUP
和 CUBE
本质上都是 GROUPING SETS
的特定语法糖。
语法:
SELECT
column1,
column2,
aggregate_function(column3)
FROM
table_name
GROUP BY
GROUPING SETS (
(column1, column2),
(column1),
(column2),
() -- 空括号表示总计
);
功能: 对 GROUPING SETS 子句中列出的每一个分组集合,分别执行一次 GROUP BY
操作,然后将所有结果联合(UNION ALL)在一起。
示例: 我们想同时获取:
- 按
region
和country
的细粒度分组合计。 - 仅按
region
的分组合计。 - 仅按
country
的分组合计。 - 全局总计。
使用 GROUPING SETS
可以一步到位:
SELECT
region,
country,
SUM(sales_amount) AS total_sales
FROM
sales
GROUP BY
GROUPING SETS (
(region, country), -- 组合1:细粒度
(region), -- 组合2:仅大区
(country), -- 组合3:仅国家
() -- 组合4:总计
)
ORDER BY
region, country;
region | country | total_sales
--------+---------+-------------
Asia | China | 15000
Asia | Japan | 11000
Asia | | 26000 <- 这是 Asia 地区的小计
Europe | Germany | 13000
Europe | UK | 9000
Europe | | 22000 <- 这是 Europe 地区的小计
| China | 15000 <- 所有地区下中国的合计
| Germany | 13000
| Japan | 11000
| UK | 9000
| | 48000 <- 这是全局总计
(11 rows)
ROLLUP:分层级小计与总计
ROLLUP
用于生成分层级或分维度的小计和总计。它假设分组列之间存在层次结构(如 年 > 月 > 日,或 大区 > 国家 > 城市)。
语法:
GROUP BY ROLLUP (column1, column2, ..., columnN)
它等价于:
GROUP BY GROUPING SETS (
(column1, column2, ..., columnN), -- 最细粒度
(column1, column2, ...),
...,
(column1), -- 第一层小计
() -- 总计
)
功能: 从最细的粒度开始,从右向左逐级减少分组列,并生成小计,最后生成一个总计。
示例:
我们想分析 region
> country
> product_category
的层级销售情况,并得到每个层级的小计。
SELECT
region,
country,
product_category,
SUM(sales_amount) AS total_sales
FROM
sales
GROUP BY
ROLLUP (region, country, product_category)
ORDER BY
region, country, product_category;
结果解读:
region | country | product_category | total_sales
--------+---------+------------------+-------------
Asia | China | Clothing | 5000 -> 5000 (最细粒度)
Asia | China | Electronics | 10000 -> 10000 (最细粒度)
Asia | China | | 15000 -> **15000** (中国的小计)
Asia | Japan | Clothing | 3000 -> 3000
Asia | Japan | Electronics | 8000 -> 8000
Asia | Japan | | 11000 -> **11000** (日本的小计)
Asia | | | 26000 -> **26000** (Asia 地区的小计)
Europe | Germany | Clothing | 4000
Europe | Germany | Electronics | 9000
Europe | Germany | | 13000
Europe | UK | Clothing | 2000
Europe | UK | Electronics | 7000
Europe | UK | | 9000
Europe | | | 22000
| | | 48000 -> **48000** (全局总计)
CUBE:生成所有组合的小计
CUBE
的功能最强大,它会生成所有可能的分组列组合。如果你的维度之间是平级的,没有明确的层次关系,并且你想分析所有维度的交叉情况,CUBE
是最佳选择。
语法:
GROUP BY CUBE (column1, column2, ..., columnN)
对于 N 列,CUBE
会生成 2^N 种分组组合。例如 CUBE(a, b, c)
等价于:
GROUP BY GROUPING SETS (
(a, b, c),
(a, b),
(a, c),
(b, c),
(a),
(b),
(c),
()
)
功能: 生成所有可能的子集组合的分组聚合。
示例:
我们想对 region
、country
、product_category
三个维度进行全交叉分析。
SELECT
region,
country,
product_category,
SUM(sales_amount) AS total_sales
FROM
sales
GROUP BY
CUBE (region, country, product_category)
ORDER BY
region, country, product_category;
结果解读(除了ROLLUP的结果,还会多出):
region | country | product_category | total_sales
--------+---------+------------------+-------------
Asia | China | Clothing | 5000
Asia | China | Electronics | 10000
Asia | China | | 15000 -> 所有产品类别下中国的销售额 (15000)
Asia | Japan | Clothing | 3000
Asia | Japan | Electronics | 8000
Asia | Japan | | 11000
Asia | | Clothing | 8000
Asia | | Electronics | 18000
Asia | | | 26000
Europe | Germany | Clothing | 4000
Europe | Germany | Electronics | 9000
Europe | Germany | | 13000 -> 所有产品类别下德国的销售额 (13000)
Europe | UK | Clothing | 2000
Europe | UK | Electronics | 7000
Europe | UK | | 9000
Europe | | Clothing | 6000
Europe | | Electronics | 16000
Europe | | | 22000
| China | Clothing | 5000
| China | Electronics | 10000
| China | | 15000
| Germany | Clothing | 4000
| Germany | Electronics | 9000
| Germany | | 13000
| Japan | Clothing | 3000
| Japan | Electronics | 8000
| Japan | | 11000
| UK | Clothing | 2000
| UK | Electronics | 7000
| UK | | 9000
| | Clothing | 14000 -> 所有地区所有国家的服装总额 (5000+3000+2000+4000=14000)
| | Electronics | 34000 -> 所有地区所有国家的电子产品总额
| | | 48000
区分原始NULL与分组产生的NULL:GROUPING() 函数
当数据本身存在 NULL
值时,我们无法区分结果中的 NULL
是来自原始数据还是分组功能生成的。ProtonBase 提供了 GROUPING()
函数来解决这个问题。
语法: GROUPING(column_name)
- 返回
0
:表示该列是当前分组的一部分(其中的NULL
是原始数据)。 - 返回
1
:表示该列因分组操作被聚合掉了(其中的NULL
是小计/总计生成的)。
高级示例: 使用 GROUPING()
美化输出。
SELECT
CASE
WHEN GROUPING(region) = 1 THEN 'All Regions'
ELSE COALESCE(region, 'N/A')
END AS region,
CASE
WHEN GROUPING(country) = 1 THEN 'All Countries'
ELSE COALESCE(country, 'N/A')
END AS country,
SUM(sales_amount) AS total_sales,
-- 这是一个方便的位掩码,表示分组状态
GROUPING(region, country) AS grouping_mask
FROM
sales
GROUP BY
ROLLUP (region, country)
ORDER BY
region, country;
结果:
region | country | total_sales | grouping_mask
-------------+---------------+-------------+---------------
All Regions | All Countries | 48000 | 3
Asia | All Countries | 26000 | 1
Asia | China | 15000 | 0
Asia | Japan | 11000 | 0
Europe | All Countries | 22000 | 1
Europe | Germany | 13000 | 0
Europe | UK | 9000 | 0
GROUPING(region, country)
返回一个整数,其二进制位表示每个字段的分组状态。例如 3
的二进制是 11
,表示第一列 (region
) 和第二列 (country
) 都被聚合了。
总结与选择指南
特性 | GROUPING SETS | ROLLUP | CUBE |
---|---|---|---|
灵活性 | 极高,可指定任意组合 | 中等,遵循层次结构 | 高,生成所有组合 |
输出行数 | 自定义 | 较少 (N+1层) | 最多 (2^N 种组合) |
适用场景 | 需要特定、非连续的聚合组合 | 有明确层次结构的报表(如地理、时间层级) | 多维交叉分析,探索所有维度关系 |
性能 | 取决于指定的集合数量 | 通常优于CUBE | 最耗资源,维度多时需谨慎 |
选择建议:
- 需要总计和分层小计(如每个大区、每个国家的合计)时,用
ROLLUP
。 - 需要所有维度的所有可能组合的聚合时(如同时看大区合计、产品合计、大区与产品的交叉合计),用
CUBE
。 - 只需要其中某几个特定组合的聚合时(如同时看按大区聚合和按产品聚合,但不需要它们的交叉组合),用
GROUPING SETS
,性能最好。