1. 程式人生 > >GROUP BY GROUPING SETS

GROUP BY GROUPING SETS

GROUPING SETS 子句是 SELECT 語句的 GROUP BY 子句的擴充套件。通過 GROUPING SETS 子句,您可採用多種方式對結果分組,而不必使用多個 SELECT 語句來實現這一目的。這就意味著,能夠減少響應時間並提高效能。

例如,以下兩條查詢語句在語義上是等效的。不過,第二個查詢通過使用 GROUP BY GROUPING SETS 子句能夠更有效地定義分組條件。

使用多個 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 的多個分組:

 
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 中找到的明確值。

因為 NULL 用作佔位符,所以很容易將佔位符 NULL 與資料中找到的真正的 NULL 相混淆。為有助於將佔位符 NULL 與 NULL 資料區分開來,請使用 GROUPING 函式。

 另請參見

 示例

下面的示例說明了如何使用 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 錯誤故障排除