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的。
---------------------
使用 GROUPING SETS 的 GROUP BY 子句能夠生成一個等效於由多個簡單 GROUP BY 子句的 UNION ALL 生成的結果集。GROUPING SETS 能夠生成等效於由簡單 GROUP BY、ROLLUP 或 CUBE 操做生成的結果。GROUPING SETS、ROLLUP 或 CUBE 的不一樣組合能夠生成等效的結果集。
本主題提供了 GROUPING SETS 等效項的示例。這些示例中使用瞭如下縮寫:
Agg():任何聚合函數
(arg):參數
指定 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 () |
如下子句可返回相同的單個結果集:
複製 GROUP BY GROUPING SETS ( (C1, C2, ..., Cn) ) |
複製 GROUP BY C1, C2, ..., Cn |
輸入列表爲 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) ,() ) |
輸入列表爲 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(A, (C1, C2, ..., Cn) ) |
複製 ROLLUP( (A), (C1, C2, ..., Cn) ) |
複製 GROUPING SETS ( (A, C1, C2, ..., Cn), (A), () ) |
如下子句是等效的:
複製 CUBE(A, (C1, C2, ..., Cn) ) |
複製 CUBE( (A), (C1, C2, ..., Cn) ) |
複製 GROUPING SETS ( (), (A), (C1, C2, ..., Cn), (A, C1, C2, ..., Cn) ) |
如下子句是等效的:
複製 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) ) |
如下子句是等效的:
複製 GROUP BY GROUPING SETS ( (A), ROLLUP (B, C) ) |
複製 GROUP BY GROUPING SETS ( (A), (B,C), (B), () ) |
如下子句是等效的:
複製 GROUP BY GROUPING SETS(A, (B, ROLLUP(C, D)) ) |
複製 GROUP BY GROUPING SETS (A, B, (B,C), (B, C, D) () ) |