PostgreSQL技术问答-17 复杂聚合

本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:

《PostgreSQL技术问答-00 Why Postgres》

文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。

本文主要讨论的内容是PostgreSQL中聚合操作的复杂形式,笔者称之为: 复杂聚合。

什么是复杂聚合

复杂聚合不是一个正式的Postgres或者SQL的技术术语,是笔者对于这一类功能和操作的一个总结和命名。在官方技术文档中,其实这一类操作包括三个主要功能,就是Grouping Sets(分组集合),Cube(数据立方)和Rollup(汇总)。

笔者已经在另一篇博文中讨论过和聚合计算相关的问题。其实在里面有一个重要的问题没有涉及,就是普通的聚合计算,都是依据聚合计算所涉及的字段进行汇总的。但现实的业务需求,可能需要对多个聚合字段再次进行汇总或者聚合,常规的聚合计算SQL语句就不能满足这个需求了。当然,开发者可以在外部程序中,根据聚合结果集合进行再次,但我们希望可以在同一个SQL语句和处理中来解决这个问题。这时,就可能需要使用复杂聚合相关的功能了。

下面是一个简单的例子:

sql 复制代码
// 按照品牌和规格进行汇总时
with D(store, brand,size,sales) as ( values
(1,'Apple',14, 10),
(2,'Apple',14, 8),
(1,'Apple',15, 20),
(1,'Dell',14, 15),
(1,'Dell',15, 5),
(2,'Dell',15, 9)
) select brand, size, sum(sales) sales from D group by 1,2;

 brand | size | sales 
-------+------+-------
 Dell  |   14 |    15
 Dell  |   15 |    14
 Apple |   15 |    20
 Apple |   14 |    18
(4 rows)

// 只按照品牌进行汇总
with D(store, brand,size,sales) as ( values
(1,'Apple',14, 10),
(2,'Apple',14, 8),
(1,'Apple',15, 20),
(1,'Dell',14, 15),
(1,'Dell',15, 5),
(2,'Dell',15, 9)
) select brand, sum(sales) sales from D group by 1;
 brand | sales 
-------+-------
 Dell  |    29
 Apple |    38
(2 rows)

这个例子,前面,基于品牌和规格汇总了销售的数量,但如果我们需要进一步的汇总品牌的销售数量,可能就需要进行再次的外部汇总,或者重新只以品牌作为汇总依据进行计算,那就是后面另外一个SQL语句了。

我们当然希望,能使用一个语句和操作就完成这种多层次汇总的操作,就需要用到复杂聚合,这是一种更强大和灵活数据分析模式。其实,在Excel里面也有类似的思想和功能,就是所谓的"数据透视表(Prvot Table)"。PostgreSQL提供了几个相关的功能,来对其进行实现。我们后面进行详细讨论。

什么是Grouping Sets

首先是Grouping Sets,直译为分组集合,就是在聚合计算中,通过指定需要的特定的分组聚合字段,来控制聚合的结果。下面是一个简单的例子:

sql 复制代码
// 品牌和规格小计,总计
 select brand, size,  sum(sales) sales from D 
 group by grouping sets((brand,size),(brand),(size),());
 brand | size | sales 
-------+------+-------
       |      |    67
 Dell  |   14 |    15
 Dell  |   15 |    14
 Apple |   15 |    20
 Apple |   14 |    18
 Dell  |      |    29
 Apple |      |    38
       |   14 |    33
       |   15 |    34
(9 rows)

// 只看品牌和总计
select brand, sum(sales) sales from D group by grouping sets((brand),());
 brand | sales 
-------+-------
       |    67
 Dell  |    29
 Apple |    38
(3 rows)

注意这里的语法规则。grouping sets关键字跟在group by子句前缀之后,表明使用一个数据集的定义。数据集定义可以使用字段名和字段名的组合,或者使用()表示完全汇总。然后的汇总操作,就会根据这个定义,分别在不同的级别和范围内进行汇总操作计算。开发者可以完全灵活的组合所需要的汇总项目和级别。

Grouping Sets好像已经够用了,为什么需要Rollup和Cube呢

前面看到了Grouping Sets,因为可以自己定义所有可能的分组聚合计算的方式和范围,这已经基本上可以满足任意分组聚合的需求了,为什么Postgres还提供了另外两个Rollup和Cube聚合方式呢?先来看看Rollup和Cube的用法:

sql 复制代码
// rollup 汇总
 select store, brand, size,  sum(sales) sales from D group by rollup(store, brand, size);
 store | brand | size | sales 
-------+-------+------+-------
       |       |      |    67
     1 | Apple |   14 |    10
     1 | Dell  |   15 |     5
     2 | Apple |   14 |     8
     2 | Dell  |   15 |     9
     1 | Dell  |   14 |    15
     1 | Apple |   15 |    20
     1 | Dell  |      |    20
     1 | Apple |      |    30
     2 | Dell  |      |     9
     2 | Apple |      |     8
     1 |       |      |    50
     2 |       |      |    17
(13 rows)

// 立方
select store, brand, size,  sum(sales) sales from D group by cube(store, brand, size);
 store | brand | size | sales 
-------+-------+------+-------
       |       |      |    67
     1 | Apple |   14 |    10
     1 | Dell  |   15 |     5
     2 | Apple |   14 |     8
     2 | Dell  |   15 |     9
     1 | Dell  |   14 |    15
     1 | Apple |   15 |    20
     1 | Dell  |      |    20
     1 | Apple |      |    30
     2 | Dell  |      |     9
     2 | Apple |      |     8
     1 |       |      |    50
     2 |       |      |    17
       | Dell  |   14 |    15
       | Dell  |   15 |    14
       | Apple |   15 |    20
       | Apple |   14 |    18
       | Dell  |      |    29
       | Apple |      |    38
     2 |       |   15 |     9
     1 |       |   15 |    25
     2 |       |   14 |     8
     1 |       |   14 |    25
       |       |   14 |    33
       |       |   15 |    34
(25 rows)

上面这个例子,让我们清晰的看到Rollup和Cube的差异。为了更好的说明问题,我们需要引入store这个汇总项目。首先我们可以看到,它们其实都是grouping sets的简化定义方式;Rollup是从定义列表开始出发,逐个项目进行汇总的分解,有一定的层次关系;而Cube,就像它的名字一样,就是从所有的维度来对数据进行监视,会将列表中所有字段进行组合,列举所有可能的汇总方式。

从技术文档上,我们也可以看到,Rollup和Cube都是Grouping sets的等效和简化书写模式,但实例和代码让我们可以更容易理解:

js 复制代码
ROLLUP ( e1, e2, e3, ... ) 等效于
GROUPING SETS (
    ( e1, e2, e3, ... ),
    ...
    ( e1, e2 ),
    ( e1 ),
    ( )
)

CUBE ( a, b, c ) 等效于:
GROUPING SETS (
    ( a, b, c ),
    ( a, b    ),
    ( a,    c ),
    ( a       ),
    (    b, c ),
    (    b    ),
    (       c ),
    (         )
)

所以,Rollup和Cube其实就是Grouping Sets的语法糖,它们可以简化分组计算的SQL语句书写方式。但如何只需要对特定的字段进行分组,还是需要使用Grouping Sets的。

能对多个聚合字段再进行组合吗

是可以的,这几个复杂聚合,都支持在Group By 子句中的组合使用。例如:

js 复制代码
GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e)) 等效于:

GROUP BY GROUPING SETS (
    (a, b, c, d), (a, b, c, e),
    (a, b, d),    (a, b, e),
    (a, c, d),    (a, c, e),
    (a, d),       (a, e)
)

可以看到,其实Grouping Sets本身可以处理任意组合的状况,但结合Rollup和Cube可以简化代码和书写。当然道理上,我们应该基于业务需求,只对需要进行计算的维度进行聚合计算。如果对聚合性能不是特别在意的话,最简单的方式就是直接使用Cube(1,2,3..)。

小结

本文探讨了聚合操作Group By的一个遗留和扩展的问题,就是如何对聚合计算结果进行再次高维度聚合的问题。在PostgreSQL中,通过Grouping Set、Rollup和Cube方法,提供了对应的功能,文中举例说明和探讨了这几种方案的联系和差异,以及它们可以适应的不同业务和应用场景。

相关推荐
liliangcsdn1 小时前
如何使用python创建和维护sqlite3数据库
数据库·sqlite
QX_hao5 小时前
【Go】--map和struct数据类型
开发语言·后端·golang
MC丶科6 小时前
【SpringBoot 快速上手实战系列】5 分钟用 Spring Boot 搭建一个用户管理系统(含前后端分离)!新手也能一次跑通!
java·vue.js·spring boot·后端
G探险者6 小时前
为何一个系统上线要经过N轮测试?带你看懂企业级发布体系
后端
TDengine (老段)7 小时前
TDengine 数学函数 DEGRESS 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)7 小时前
TDengine 数学函数 GREATEST 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
安当加密8 小时前
云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
数据库·微服务·云原生
lang201509288 小时前
Spring Boot 入门:5分钟搭建Hello World
java·spring boot·后端
爱喝白开水a8 小时前
LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
开发语言·数据库·人工智能·python·langchain·prompt·知识图谱