同时得到合计行
如果想要获得同时得出小计和合计的结果,通常的做法是分别计算出合计行和按照商品种类进行汇总的结果,然后通过UNION ALL连接在一起。
1 | SELECT '合计' AS product_type, SUM(sale_price) |
这样一来,为了得到想要的结果,需要执行两次几乎相同的SELECT语句,再将其结果进行连接,不但看上去十分繁琐,而且DBMS内部的处理成本也非常高。
ROLLUP——同时得出合计和小计
标准SQL引入了GROUPING运算符。使用该运算符就能通过非常简单的SQL得到之前那样的汇总单位不同的汇总结果了。
GROUPING运算符包含以下3种:
- ROLLUP
- CUBE
- GROUPING SETS
ROLLUP的使用方法
使用ROLLUP就可以通过非常简单的SELECT语句同时计算出合计行了。
1 | SELECT product_type, SUM(sale_price) AS sum_price |
从语法上来说,就是将GROUP BY子句中的聚合键清单像ROLLUP(<列1>,<列2>,…)这样使用。该运算符的作用,就是“一次计算出不同聚合键组合的结果”。例如,在本例中就是一次计算出了如下两种组合的汇总结果。
- GROUP BY ()
- GROUP BY (product_type)
1中的GROUP BY()表示没有聚合键,也就相当于没有GROUP BY子句(这时会得到全部数据的合计行的记录),该合计行记录称为超级分组记录。超级分组记录的product_type列的键值(对DBMS来说)并不明确,因此会默认使用NULL。
将“登记日期”添加到聚合键当中
下面再添加一个聚合键“登记日期(regist_date)”试试看吧。
1 | SELECT product_type, regist_date, SUM(sale_price) AS sum_price |
将上述两个结果进行比较后发现,使用ROLLUP时多出了最上方的合计行以及3条不同商品种类的小计行(也就是未使用登记日期作为聚合键的记录),这4行就是超级分组记录。也就是说,该SELECT语句的结果相当于使用UNION对如下3种模式的聚合级的不同结果进行连接。
- GROUP BY ()
- GROUP BY (product_type)
- GROUP BY (product_type, regist_date)
ROLLUP是“卷起”的意思。其名称也形象地说明了该操作能够得到像从小计到合计这样,从最小的聚合级开始,聚合单位逐渐扩大的结果。
GROUPING函数——让NULL更加容易分辨
之前使用ROLLUP所得到的结果有些蹊跷,问题就出在“衣服”的分组之中,有两条记录的regist_date为NULL,但其原因却并不相同。
sum_price为4000日元的记录,因为商品表中运动T恤的注册日期为NULL,所以就把NULL作为聚合键了。
相反,sum_price为5000日元的记录毫无疑问就是超级分组记录的NULL了。但两者看上去都是“NULL“,实在是难以分辨。
为了避免混淆,SQL提供了一个用来判断超级分组记录的NULL的特定函数——GROUPING函数。该函数在其参数列的值为超级分组记录所产生的NULL时返回1,其他情况返回0。
1 | SELECT GROUPING(product_type) AS product_type, |
这样就能分辨超级分组记录中的NULL和原始数据本身的NULL了。使用GROUPING函数还能在超级分组记录的键值中插入字符串。也就是说,当GROUPING函数的返回值为1时,指定“合计”或者“小计”等字符串,其他情况返回通常的列的值。
1 | SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计' |
那为什么还要将SELECT子句中的regist_date列转换为CAST(regist_date AS VARCHAR(16))形式的字符串呢?
这是为了满足CASE表达式所有分支的返回值必须一致的条件。如果不这样的话,那么各个分支会分别返回日期类型和字符串类型的值,执行时就会发生语法错误。
CUBE——用数据来搭积木
ROLLUP之后来介绍另一个常用的GROUPING运算符——CUBE。CUBE的语法和ROLLUP相同,只需要将ROLLUP替换为CUBE就可以了。
1 | SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计' |
与ROLLUP的结果相比,CUBE的结果中多出了几行记录。多出来的记录就是只把regist_date作为聚合键所得到的汇总结果。
- GROUP BY ()
- GROUP BY (product_type)
- GROUP BY (regist_date) <- 添加的组合
- GROUP BY (product_type, regist_date)
所谓CUBE,就是将GROUP BY子句中聚合键的“所有可能的组合”的汇总结果集中到一个结果中。因此,组合的个数就是2^n(n是聚合键的个数)。本例中聚合键有2个,所以2^2=4。
究竟CUBE运算符和立方体有什么关系呢?
众所周知,立方体由长、宽、高3个轴构成。对于CUBE来说,一个聚合键就相当于其中的一个轴,而结果就是将数据像积木那样堆积起来。
GROUPING SETS取得期望的积木
最后要介绍的GROUPING运算符是GROUPING SETS。该运算符可以用于从ROLLUP或者CUBE的结果中取出部分记录。
例如,之前的CUBE的结果就是根据聚合键的所有可能的组合计算而来的。如果希望从中选取出将“商品种类”和“登记日期”各自作为聚合键的结果,或者不想得到“合计记录和使用2个聚合键的记录”时,可以使用GROUPING SETS。
1 | SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计' |
与ROLLUP或者CUBE能够得到规定的结果相对,GROUPING SETS用于从中取出个别条件对应的不固定的结果。然而,由于期望获得不固定结果的情况少之又少,因此与ROLLUP或者CUBE比起来,使用GROUPING SETS的机会也就很少了。