GROUP BY GROUPING SETS

GROUPING SETS 子句是 SELECT 语句的 GROUP BY 子句的扩展。经过 GROUPING SETS 子句,您可采用多种方式对结果分组,而没必要使用多个 SELECT 语句来实现这一目的。这就意味着,可以减小响应时间并提升性能。html

例如,如下两条查询语句在语义上是等效的。不过,第二个查询经过使用 GROUP BY GROUPING SETS 子句可以更有效地定义分组条件。sql

使用多个 SELECT 语句的多个分组:函数


 
SELECT NULL, NULL, NULL, COUNT( * ) AS Cnt
FROM Customers
WHERE State IN ( 'MB' , 'KS' )
   UNION ALL
SELECT City, State, NULL, COUNT( * ) AS Cnt
FROM Customers
WHERE State IN ( 'MB' , 'KS' )
GROUP BY City, State
   UNION ALL
SELECT NULL, NULL, CompanyName, COUNT( * ) AS Cnt
FROM Customers
WHERE State IN ( 'MB' , 'KS' )
GROUP BY CompanyName;

使用 GROUPING SETS 的多个分组:oop

 
SELECT City, State, CompanyName, COUNT( * ) AS Cnt
FROM Customers
WHERE State IN ( 'MB' , 'KS' )
GROUP BY GROUPING SETS( ( City, State ), ( CompanyName ) , ( ) );

两种方法均产生相同的结果,以下所示:性能

  City State CompanyName Cnt
1 (NULL) (NULL) (NULL) 8
2 (NULL) (NULL) Cooper Inc. 1
3 (NULL) (NULL) Westend Dealers 1
4 (NULL) (NULL) Toto's Active Wear 1
5 (NULL) (NULL) North Land Trading 1
6 (NULL) (NULL) The Ultimate 1
7 (NULL) (NULL) Molly's 1
8 (NULL) (NULL) Overland Army Navy 1
9 (NULL) (NULL) Out of Town Sports 1
10 'Pembroke' 'MB' (NULL) 4
11 'Petersburg' 'KS' (NULL) 1
12 'Drayton' 'KS' (NULL) 3

第 2-9 行是按照 CompanyName 分组生成的行,第 10-12 行是按照 City 和 State 的组合进行分组所生成的行,第 1 行是空分组集所表示的总计,它是使用一对成对的圆括号 () 指定的。空分组集表示 GROUP BY 输入中全部行的单个分区。学习

请注意 NULL 值如何在分组集中不使用的表达式中充当占位符,由于这些结果集必须能够组合。例如,第 2-9 行由查询 (CompanyName) 中的第二个分组集获得。由于分组集未将 City 或 State 做为表达式包含在内,因此对于第 2-9 行,City 和 State 的值中会含有占位符 NULL,而 CompanyName 中的值将含有在 CompanyName 中找到的明确值。ui

由于 NULL 用做占位符,因此很容易将占位符 NULL 与数据中找到的真正的 NULL 相混淆。为有助于将占位符 NULL 与 NULL 数据区分开来,请使用 GROUPING 函数。spa

 另请参见3d

 示例code

下面的示例说明了如何使用 GROUPING SETS 定制从查询返回的结果,以及如何使用 ORDER BY 子句更好地组织这些结果。如下查询将按各年份 (Year) 中的季度 (Quarter) 返回订单总数以及各年份 (Year) 的总数。先按年份 (Year) 排序,再按季度 (Quarter) 排序可以使结果更易于理解:

 
SELECT Year( OrderDate ) AS Year, 
        Quarter( OrderDate ) AS Quarter, 
        COUNT (*) AS Orders 
FROM SalesOrders
GROUP BY GROUPING SETS ( ( Year, Quarter ), ( Year ) )
ORDER BY Year, Quarter;

此查询会返回如下结果:

  Year Quarter Orders
1 2000 (NULL) 380
2 2000 1 87
3 2000 2 77
4 2000 3 91
5 2000 4 125
6 2001 (NULL) 268
7 2001 1 139
8 2001 2 119
9 2001 3 10

第 1 行和第 6 行分别是 2000 年和 2001 年的订单数小计。第 2-5 行和第 7-9 行是小计行的详细信息行。也就是说,它们按年、按季度显示订单总数。

结果集中没有全部年份中全部季度的总计。要实现此目的,查询必须在 GROUPING SETS 说明中包括空分组说明 '()'。

 指定空分组说明

 

若是在 GROUP BY 子句中使用空 GROUPING SETS 说明 '()',则会产生一个总计行,对结果中的全部项进行总计。使用总计行时,全部分组表达式的全部值均会包含占位符 NULL。可以使用 GROUPING 函数将占位符 NULL 与计算行底层数据中的值后产生的实际 NULL 值区分开来。

 指定重复分组集

可在 GROUPING SETS 子句中指定重复分组说明。此时,SELECT 语句的结果将包含相同的行。

如下查询包括重复分组:

 
SELECT City, COUNT( * ) AS Cnt
FROM Customers
WHERE State IN ( 'MB' , 'KS' )
GROUP BY GROUPING SETS( ( City ), ( City ) );

此查询会返回如下结果。请注意,因为重复分组的缘由,第 1-3 行与第 4-6 行相同:

  City Cnt
1 'Drayton' 3
2 'Petersburg' 1
3 'Pembroke' 4
4 'Drayton' 3
5 'Petersburg' 1
6 'Pembroke' 4

 实践良好的格式

GROUP BY GROUPING SETS 子句的分组语法的解释方式不一样于简单的 GROUP BY 子句。例如,GROUP BY (X, Y) 返回按 X 和 Y 值的惟一组合进行分组的结果。而 GROUP BY GROUPING SETS (X, Y) 将指定两个单独的分组集,且这两个分组的结果会结合在一块儿。也就是说,结果将按 (X) 分组,而后合并到按 (Y) 分组的相同结果中。

为使格式良好,并在表达式复杂的状况下避免形成任何歧义,请在有可能出错的时候为说明的每一个单独分组集括上括号。例如,尽管如下两条语句都是正确的,且语义上等效,但建议采用第二种格式:

 
SELECT * FROM t GROUP BY GROUPING SETS ( X, Y );
SELECT * FROM t GROUP BY GROUPING SETS( ( X ), ( Y ) );

 另请参见

 若是说聚合函数(Simple UDAF / Generic UDAF)是HQL聚合数据查询或分析的中枢处理器,那GROUP BY能够说是聚合函数的神经了,GROUP BY收集和传递材料,而后交给聚合函数们去处理。这些材料的组织形式显得尤其重要,它们表达着分析者想要的观察维度或视角,管理着聚合函数们的操做对象。

       而分析者常常想要在一次分析中从多个维度去得到分析数据,对包含多个维度或多级层次的分析,上卷(roll up)或下钻(drill down)一类就颇有分析价值。

       咱们有时候能够从最细、最多的粒度去作一个查询,而后把结果集导入Excel这个数据分析利器,用数据透视图标进行“上卷”分析;但有时候也行不通,好比说UV这种须要去重的数据,在Excel里用汇总方式进行上卷,就不是纯粹的UV概念了。

       因此,对这种情形,在查询过程当中,咱们就须要得到已经下钻和上卷的数据;若是只有GROUP BY子句,那咱们能够写出按各个维度或层次进行GROUP BY的查询语句,而后再经过UNION子句把结果集拼凑起来,可是这样的查询语句显得冗长、笨拙。

       为此,HQL像其它不少SQL实现同样,为咱们提供了GROUPINGSETS子句来简化查询语句的编写,如下官方CWiki文档很清晰地表达了GROUPING SETS的功能:

 

Aggregate Query with GROUPING SETS

Equivalent Aggregate Query with GROUP BY

SELECT a, b, SUM(c) FROM tab1 GROUP BY a, b GROUPING SETS ( (a,b) )


SELECT a, b, SUM(c) FROM tab1 GROUP BY a, b
SELECT a, b, SUM( c ) FROM tab1 GROUP BY a, b GROUPING SETS ( (a,b), a)


SELECT a, b, SUM( c ) FROM tab1 GROUP BY a, b
UNION
SELECT a, null, SUM( c ) FROM tab1 GROUP BY a


SELECT a,b, SUM( c ) FROM tab1 GROUP BY a, b GROUPING SETS (a,b)


SELECT a, null, SUM( c ) FROM tab1 GROUP BY a
UNION
SELECT null, b, SUM( c ) FROM tab1 GROUP BY b


SELECT a, b, SUM( c ) FROM tab1 GROUP BY a, b GROUPING SETS ( (a, b), a, b, ( ) )


SELECT a, b, SUM( c ) FROM tab1 GROUP BY a, b
UNION
SELECT a, null, SUM( c ) FROM tab1 GROUP BY a, null
UNION
SELECT null, b, SUM( c ) FROM tab1 GROUP BY null, b
UNION
SELECT null, null, SUM( c ) FROM tab1


       由于涉及UNION操做,因此为了遵循UNION对参与合并的数据集合的要求,GROUPING SETS会把在单个GROUP BY逻辑中没有参与GROUP BY的那一列置为NULL值,使它成为常量占位列。这样聚合出来的结果,未被GROUP BY的列将显示为NULL。

       可是这样的处理也会引发一个歧义性问题,若是咱们分析的表有一些列没有NOT NULL约束,那原始数据中,未被GROUP BY的列可能本来就会出现一些NULL值,这样,GROUPING SETS出来的结果,咱们没有办法去区分该列显示的NULL值是原始数据出现的NULL值聚合的结果,仍是你由于这列没有参与GROUP BY而被置为NULL值的结果。

       为了解决这个歧义问题,HQL又为咱们提供了一个Grouping__ID函数(请注意函数名中的下划线是两个!);这个函数没有参数,在有GROUPING SETS子句的状况下,把它直接放在SELECT子句中,像其它列同样,独占一列。它返回的结果是一个看起来像整形数值类型,实际上是字符串的值,这个值使用了位图策略(bitvector,位向量),即它的二进制形式中的每1位标示着对应列是否参与GROUP BY,若是某一列参与了GROUP BY,对应位就被置为1,不然为0,根据这个位向量值和对应列是否显示为NULL,咱们就能够解决上面提到的歧义问题了。

       这样一来,Grouping__ID函数返回值的范围由查询的字段数(除去聚合函数产生的列)决定,若是好比有3列,那位向量为3位,最大值为7。CWiki文档提供了下面的示例:

       有下面一个表数据:

  Column1 (key)  

  Column2 (value)  

  1

  NULL

  1

  1

  2

  2

  3

  3

  3

  NULL

  4

  5

       咱们用这样的查询语句去执行查询:

SELECT key, value, GROUPING__ID, count(*) from T1 GROUP BY key,value WITH ROLLUP


       将获得以下查询结果:

 

 

 

 

  NULL  

  NULL  

    0    

    6    

     1

  NULL

    1

    2

     1

  NULL

    3

    1

     1

    1

    3

    1

     2

  NULL

    1

    1

     2

    2

    3

    1

     3

  NULL

    1

    2

     3

  NULL

    3

    1

     3

     3

    3

    1

     4

  NULL

    1

    1

     4

     5

    3

    1

 

       官方文档没有明确说明这个位向量和各列的高低位对应关系,可是从示例咱们能够看到,这个位向量的低位对应SELECT子句中的第1列(非聚合列),高位对应最后1列(非聚合列)。

       上面的查询用到了WITH ROLLUP子句,它对应SQL中的上卷操做,其实它就是GROUPINGSETS的特例,对应上面第一个表格中的第4种情形;根据官方的CWiki文档解释,GROUP BY 子句加上ROLLUP 子句可用于计算从一个维度进行层级聚合的操做:

GROUP BY a, b, c with ROLLUP assumes that the hierarchy is"a" drilling down to "b" drilling down to "c".


       相似地还有WITH CUBE子句,对应SQL中的CUBE操做,它完成对字段列中的全部可能组合(全序集?)进行GROUP BY的功能,正如官方CWiki文档的解释:

GROUP BY a, b, c WITH CUBE 等同于
GROUP BY a, b, c GROUPING SETS ( (a, b, c), (a, b), (b, c), (a, c),(a), (b), (c), ( ))

 
       GROUPING SETS加强了GROUP BY的查询表达能力,ROLLUP和CUBE又加强了GROUPING SETS的查询表达能力,这样一来,GROUP BY的形态也变得多样化了,让咱们可以在查询阶段就实现更多的分析角度。

       还需留意的是:Hive从0.10.0版本才开始有GROUPING SETS的。
--------------------- 

SQL  grouping sets 子句
grouping sets子句容许你指定多个group by 选项。加强了group by 的功能。
能够经过一条select 语句实现复杂繁琐的多条select 语句的查询。而且更加的
高效,解析存储一条SQL于语句。下面经过使用grouping sets 子句与没有
使用grouping sets 子句来对比学习该子句。
查询每一个部门的每类工做的平均工资,使用grouping sets的语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by grouping sets ((department_id,job_id));
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
          110 AC_ACCOUNT        8300
           80 SA_REP         7909.09091
           90 AD_VP                  17000
           50 ST_CLERK               2785
          110 AC_MGR              12000
           50 ST_MAN                 7280
           80 SA_MAN              12200
           50 SH_CLERK              3215
没有使用grouping sets 的对等语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by department_id,job_id;
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
          110 AC_ACCOUNT        8300
           80 SA_REP          7909.09091
           90 AD_VP                   17000
           50 ST_CLERK                2785
          110 AC_MGR               12000
           50 ST_MAN                 7280
           80 SA_MAN              12200
           50 SH_CLERK             3215
再查询每一个部门每类工做平均工资的基础上,还要查询出每一个部门的平均工资。使用grouping sets的语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by grouping sets ((department_id,job_id),department_id);
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
           10 AD_ASST              4400#每一个部门每类工做的平均工资。
           10                              4400#每一个部门的平均工资。
           20 MK_MAN           13000
           20 MK_REP               6000
           20                             9500
           30 PU_MAN           11000
           30 PU_CLERK           2780
           30                             4150
           40 HR_REP               6500
没有使用grouping sets 的对等语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by (department_id,job_id)
  4  union
  5  select department_id,null,avg(salary)
  6  from hr.employees
  7  group by department_id;
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
           10 AD_ASST              4400
           10                              4400
           20 MK_MAN           13000
           20 MK_REP               6000
           20                             9500
           30 PU_CLERK           2780
           30 PU_MAN           11000
           30                             4150
           40 HR_REP               6500
 
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by grouping sets ((department_id,job_id),department_id,());
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
           10 AD_ASST              4400
           10                              4400
           20 MK_MAN           13000
           20 MK_REP               6000
           20                             9500
           30 PU_MAN           11000
           30 PU_CLERK          2780
再查询每一个部门每类工做平均工资与查询出每一个部门的平均工资的基础上,还要查询整个企业中雇员的平均工资。使用grouping sets的语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by grouping sets ((department_id,job_id),department_id,()); #() 表示作一个总体的汇集。
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
           10 AD_ASST              4400
           10                              4400
           20 MK_MAN           13000
           20 MK_REP               6000
           20                             9500
           30 PU_MAN           11000
           30 PU_CLERK           2780
                                              。
                                              。
                                              。
                                 6380.90909 #这一行查询的是整个employees 中雇员总的平均工资。
没有使用grouping sets 的对等语句。
SQL> select department_id,job_id,avg(salary)
  2  from hr.employees
  3  group by (department_id,job_id)
  4  union
  5  select department_id,null,avg(salary)
  6  from hr.employees
  7  group by department_id
  8  union 
  9  select null,null,avg(salary)
 10  from hr.employees;
DEPARTMENT_ID JOB_ID     AVG(SALARY)
------------- ---------- -----------
           10 AD_ASST              4400
           10                              4400
           20 MK_MAN           13000
           20 MK_REP               6000
           20                              9500
           30 PU_CLERK            2780
           30 PU_MAN           11000
           30                             4150
           40 HR_REP               6500
                                              。
                                              。
                                              。
                                6380.90909 #这一行查询的是整个employees 中雇员总的平均工资。
  • 2012/04/01

GROUPING SETS 等效项

使用 GROUPING SETS 的 GROUP BY 子句能够生成一个等效于由多个简单 GROUP BY 子句的 UNION ALL 生成的结果集。GROUPING SETS 能够生成等效于由简单 GROUP BY、ROLLUP 或 CUBE 操做生成的结果。GROUPING SETS、ROLLUP 或 CUBE 的不一样组合能够生成等效的结果集。

本主题提供了 GROUPING SETS 等效项的示例。这些示例中使用了如下缩写:

  • Agg():任何聚合函数

  • (arg):参数

UNION ALL 的 GROUPING SETS 等效项

 

指定 GROUPING SETS (<分组集> [,...n]) 做为 GROUP BY 列表等效于查询的 UNION ALL,其中每一个查询将其中一个分组集做为其 GROUP BY 列表。浮点数的聚合返回的结果可能会略有不一样。

如下语句是等效的:

复制

SELECT customer, year, SUM(sales)
FROM T
GROUP BY GROUPING SETS ((customer), (year))

复制

SELECT customer, NULL as year, SUM(sales)
FROM T 
GROUP BY customer
UNION ALL
SELECT NULL as customer, year, SUM(sales)
FROM T 
GROUP BY year

简单 GROUP BY 的 GROUPING SETS 等效项

 

如下子句可返回相同的总计:

复制

GROUP BY GROUPING SETS ( () )

复制

GROUP BY ()

如下子句可返回相同的单个结果集:

复制

GROUP BY GROUPING SETS ( (C1, C2, ..., Cn) )

复制

GROUP BY C1, C2, ..., Cn

GROUPING SETS ROLLUP 等效项

 

输入列表为 n 维的 GROUP BY ROLLUP (<组合元素列表>) 等效于这样的 GROUPING SETS,其中使用其输入列表的全部前缀 (n+1) 做为其 GROUPING SETS。

如下子句是等效的:

复制

GROUP BY ROLLUP (C1, C2, …, Cn-1, Cn)

复制

GROUP BY GROUPING SETS ( (C1, C2, …, Cn-1, Cn)
    ,(C1, C2, ..., Cn-1)
    ...
    ,(C1, C2)
    ,(C1)
    ,() )

GROUPING SETS CUBE 等效项

 

输入列表为 n 维的 GROUP BY CUBE (<组合元素列表>) 等效于这样的 GROUPING SETS,其中使用其输入列表的全集(输入列表中维度的 2n 个组合)做为其 GROUPING SETS。

如下子句是等效的:

复制

GROUP BY CUBE (C1, C2, C3, ..., Cn-2, Cn-1, Cn)

复制

GROUP BY GROUPING SETS (
     (C1, C2, C3, ..., Cn-2, Cn-1, Cn) -- All dimensions are included.
    ,( , C2, C3, ..., Cn-2, Cn-1, Cn) -- n-1 dimensions are included.
    ,(C1, C3, ..., Cn-2, Cn-1, Cn)
    …
    ,(C1, C2, C3, ..., Cn-2, Cn-1,)
    ,(C3, ..., Cn-2, Cn-1, Cn) -- n-2 dimensions included
    ,(C1  ..., Cn-2, Cn-1, Cn)
    …
    ,(C1, C2) -- 2 dimensions are included.
    ,…
    ,(C1, Cn)
    ,…
    ,(Cn-1, Cn)
    ,…
    ,(C1) -- 1 dimension included
    ,(C2)
    ,…
    ,(Cn-1)
    ,(Cn)
    ,() ) -- Grand total, 0 dimension is included.

如下子句是等效的:

复制

GROUP BY CUBE (C1, C2, C3)

复制

GROUP BY GROUPING SETS ( (C1, C2, C3)
    ,(C1, C2)
    ,(C1, C3)
    ,(C2, C3)
    ,(C1)
    ,(C2)
    ,(C3)
    ,() )

内部包含分组集的 ROLLUP 中的组合列

 

如下子句是等效的:

复制

ROLLUP(A, (C1, C2, ..., Cn) )

复制

ROLLUP( (A), (C1, C2, ..., Cn) )

复制

GROUPING SETS ( (A, C1, C2, ..., Cn), (A), () )

内部包含分组集的 CUBE 中的组合列

 

如下子句是等效的:

复制

CUBE(A, (C1, C2, ..., Cn) )

复制

CUBE( (A), (C1, C2, ..., Cn) )

复制

GROUPING SETS ( (), (A), (C1, C2, ..., Cn), (A, C1, C2, ..., Cn) )

包含 GROUPING SETS、ROLLUP 或 CUBE 的 GROUP BY

 

如下子句是等效的:

复制

GROUP BY A, CUBE (B, C)

复制

GROUP BY GROUPING SETS ( (A), (A, B), (A, C), (A, B, C ))

如下子句是等效的:

复制

GROUP BY A, GROUPING SETS ( (B), (C) )

复制

GROUP BY GROUPING SETS ( (A, B), (A, C) )

如下子句是等效的:

复制

GROUP BY ROLLUP (A, B), ROLLUP(C, D)

复制

GROUP BY GROUPING SETS
    ( (),(C),(C,D),(A),(A,C),(A,C,D),(A,B),(A,B,C),(A,B,C,D) )

包含在 GROUPING SETS 列表中的 ROLLUP

 

如下子句是等效的:

复制

GROUP BY GROUPING SETS ( (A), ROLLUP (B, C) )

复制

GROUP BY GROUPING SETS ( (A), (B,C), (B), () )

包含在分组集中的 ROLLUP

 

如下子句是等效的:

复制

GROUP BY GROUPING SETS(A, (B, ROLLUP(C, D)) )

复制

GROUP BY GROUPING SETS (A, B, (B,C), (B, C, D) () )

请参阅

 

参考

GROUP BY (Transact-SQL)

GROUPING (Transact-SQL)

GROUPING_ID (Transact-SQL)

概念

将 GROUP BY 与 ROLLUP、CUBE 和 GROUPING SETS 一块儿使用

GROUP BY 错误故障排除

相关文章
相关标签/搜索