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方法,提供了对应的功能,文中举例说明和探讨了这几种方案的联系和差异,以及它们可以适应的不同业务和应用场景。

相关推荐
fobwebs3 分钟前
如何通过phpmyadmin指令来优化数据库表,给数据库“减肥”。
数据库·wordpress·数据库优化·phpmyadmin
syinfo3 分钟前
oracle使用PLSQL导出表数据
数据库·oracle
客梦5 分钟前
数据库基础
数据库·笔记
老苏畅谈运维8 分钟前
Oracle AI Database 26ai 安装实战
数据库·oracle·oracle 26ai
Rick19938 分钟前
SQL优化
数据库·sql
沪漂阿龙9 分钟前
掌握MySQL这些函数,SQL水平直接起飞!
数据库·sql·mysql
小松加哲9 分钟前
# Spring Aware 与 BeanPostProcessor:作用、使用与原理(源码级)
java·后端·spring
无忧智库10 分钟前
破局与重构:基于“智慧大脑”的企业全面数据化经营深度解构(PPT)
数据库·重构
大嘴皮猴儿17 分钟前
零基础入门:跨境电商产品图片多语言翻译的完整流程与跨马翻译实操
大数据·数据库·人工智能·自动翻译·教育电商
雷工笔记20 分钟前
Navicat 备份与还原 PostgreSQL 数据库
数据库·postgresql·oracle