《SQL Server溫故系列》之分組查詢 GROUP BY。GROUP BY 是一種能將查詢結果劃分為多個行組的查詢語句的子句,其目的通常是為了在每個組上執行一個或多個聚合運算,所以 GROUP BY 通常會與聚合函數一塊兒出現在查詢語句中。本文主要講述了 SQL Server 中 GROUP B... ...
1、GROUP BY 與聚合函數
GROUP BY 是一種能將查詢結果劃分為多個行組的查詢語句的子句,其目的通常是為了在每個組上執行一個或多個聚合運算,所以 GROUP BY 通常會與聚合函數一塊兒出現在查詢語句中。
GROUP BY 的標準分組方式是按所有分組欄位的值依次來分組。假如欄位 A 的值有 3 種,欄位 B 的值有 2 種;如果是GROUP BY A
,那麼就會被分為 3 組;而如果是GROUP BY A,B
,那麼就會先被 A 分為 3 組,然後這 3 組又會被 B 再各自分為 2 組,最終會被分為 3×2 等於 6 組。
顯然,GROUP BY B,A
最終也會被分為 6 組,換而言之,標準分組時的欄位的順序不會對分組結果產生影響。但分組欄位的順序會影響查詢結果的排序,如果想要改變結果集的排序,可以通過 ORDER BY 子句來實現。
示例一、查詢統計學生 1、2、3 的第 1 次考試成績,且按各科總分降序排列:
SELECT t.StudentId,COUNT(1) 科目數,
SUM(t.Scores) 總分,MAX(t.Scores) 最高分,MIN(t.Scores) 最低分,AVG(t.Scores) 平均分
FROM T_ExamResults t
WHERE t.Counts = 1 AND t.StudentId IN(1,2,3)
GROUP BY t.StudentId
ORDER BY 總分 DESC;
示例二、查詢統計學生 1、2、3 的第 1 次考試成績,且按班級名稱和學生名稱來升序排列:
SELECT t1.Code,t1.Name,t3.Name,COUNT(1) 科目數,
SUM(t2.Scores) 總分,MAX(t2.Scores) 最高分,MIN(t2.Scores) 最低分,AVG(t2.Scores) 平均分
FROM T_Students t1
JOIN T_ExamResults t2 ON t1.Id = t2.StudentId AND t2.Counts = 1
JOIN T_Classes t3 ON t1.ClassId = t3.Id
WHERE t1.Id IN(1,2,3)
GROUP BY t1.Code,t1.Name,t3.Name
ORDER BY t3.Name,t1.Name DESC;
註意:在含有 GROUP BY 子句的查詢語句中,每組只會返回一行數據,且查詢選擇列表中的列只能是 GROUP BY 中的欄位或聚合函數表達式。
2、GROUP BY 與 HAVING
HAVING 子句的作用有點類似於 WHERE 子句,說到底它們都是過濾數據用的,但不同的是,WHERE 子句過濾的最小單位是數據行,而 HAVING 子句過濾的最小單位是行組。相較於 WHERE 子句,HAVING 子句最大的優勢就是支持聚合函數。
HAVING 子句只能在查詢語句中使用,且通常與 GROUP BY 子句一起使用。如果查詢語句中沒有 GROUP BY 子句,那麼就會有隱式的單一行組,但這通常是沒有意義的。例如要查詢統計平均分達到 80 分的學生第 1 次考試成績,且按總分倒序排列,示例如下:
SELECT t.StudentId,COUNT(1) 科目數,
SUM(t.Scores) 總分,MAX(t.Scores) 最高分,MIN(t.Scores) 最低分,AVG(t.Scores) 平均分
FROM T_ExamResults t
WHERE t.Counts = 1
GROUP BY t.StudentId
HAVING AVG(t.Scores) >= 80
ORDER BY SUM(t.Scores) DESC;
3、GROUP BY 擴展分組
在實際的開發工作中,尤其是開發數據報表,往往需要統計多維度的小計和合計。大多數情況下用 UNION 也能達到類似效果,但實現起來比較繁瑣,靈活性較差,性能往往也比較低。針對這類需求,SQL Server 提供了幾個實用的擴展分組,以便能更好的實現這些需求。
3.1、GROUP BY ROLLUP
ROLLUP 是對 GROUP BY 子句的一種擴展,它允許計算標準分組及部分維度的小計及合計。ROLLUP 的計算結果與分組欄位的順序有關,因為它的分組過程具有方向性,先計算標準分組,然後從右到左遞減計算更高一級的小計,直到所有欄位被計算完,最後計算合計。
對於GROUP BY ROLLUP(a,b,c)
,結果具有 (a,b,c)、(a,b,NULL)、(a,NULL,NULL)、(NULL,NULL,NULL) 唯一值的組。換而言之,GROUP BY ROLLUP(a,b,c)
的結果集就等價於GROUP BY a,b,c
的結果集,加上GROUP BY a,b
的結果集,再加上GROUP BY a
的結果集,最後加上不帶GROUP BY
的總計結果集。
示例一、查詢統計 1、2、3 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId
FROM T_Students t
WHERE t.ClassId IN(1,2,3)
)
SELECT t.ClassId,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY ROLLUP(t.ClassId);
查詢結果如下:
ClassId 學生個數 最大年齡 最小年齡
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
3 9 21 15
NULL 28 21 15
示例二、查詢統計 1、2 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY ROLLUP(t.ClassId,t.Gender);
查詢結果如下:
ClassId Gender 學生個數 最大年齡 最小年齡
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16
NULL NULL 19 20 15
示例三、查詢統計 1、2 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY t.ClassId,ROLLUP(t.Gender);
查詢結果如下:
ClassId Gender 學生個數 最大年齡 最小年齡
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16
3.2、GROUP BY CUBE
CUBE 是對 GROUP BY 子句的一種擴展,它允許計算標準分組及所有維度的小計及合計。CUBE 會對所有可能的分組進行統計,從而生成交叉報表。CUBE 比 ROLLUP 的分組更多,完全包含了 ROLLUP 的統計結果,且計算結果與分組欄位的順序無關,但如果欄位順序不同,預設的結果集排序會有不同。
對於GROUP BY CUBE(a,b)
,結果具有 (a,b)、(a,NULL)、(NULL,b)、(NULL,NULL) 唯一值的組。換而言之,GROUP BY CUBE(a,b)
的結果集就等價於GROUP BY a,b
的結果集,加上GROUP BY a
的結果集,再加上GROUP BY b
的結果集,最後加上不帶GROUP BY
的總計結果集。
示例一、查詢統計 1、2、3 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId
FROM T_Students t
WHERE t.ClassId IN(1,2,3)
)
SELECT t.ClassId,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY CUBE(t.ClassId);
查詢結果如下:
ClassId 學生個數 最大年齡 最小年齡
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
3 9 21 15
NULL 28 21 15
示例二、查詢統計 1、2 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY CUBE(t.ClassId,t.Gender);
查詢結果如下:
ClassId Gender 學生個數 最大年齡 最小年齡
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
2 0 4 20 17
NULL 0 10 20 15
1 1 4 20 18
2 1 5 20 16
NULL 1 9 20 16
NULL NULL 19 20 15
1 NULL 10 20 15
2 NULL 9 20 16
示例三、查詢統計 1、2 班的學生個數及年齡:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,t.Gender,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY t.ClassId,CUBE(t.Gender);
查詢結果如下:
ClassId Gender 學生個數 最大年齡 最小年齡
----------- ----------- ----------- ----------- -----------
1 0 6 19 15
1 1 4 20 18
1 NULL 10 20 15
2 0 4 20 17
2 1 5 20 16
2 NULL 9 20 16
3.3、GROUP BY GROUPING SETS
GROUPING SETS 是對 GROUP BY 子句的一種擴展,它允許一次計算多個標準分組的小計。GROUPING SETS 的功能相當於將多個 GROUP BY 子句組合到一個 GROUP BY 子句中,類似於用 UNION ALL 合併多個 GROUP BY 的結果集,所以它的計算結果與排序欄位的順序無關,而且不會合併重覆組。
例如GROUP BY GROUPING SETS(ROLLUP(A))
和GROUP BY ROLLUP(A)
的結果集相同,GROUP BY GROUPING SETS(A,B)
和GROUP BY A
加GROUP BY B
的結果集相同。示例如下:
WITH t AS(
SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age,t.ClassId,t.Gender
FROM T_Students t
WHERE t.ClassId IN(1,2)
)
SELECT t.ClassId,COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡
FROM t
GROUP BY GROUPING SETS(ROLLUP(t.ClassId),CUBE(t.ClassId));
查詢結果如下:
ClassId 學生個數 最大年齡 最小年齡
----------- ----------- ----------- -----------
1 10 20 15
2 9 20 16
NULL 19 20 15
1 10 20 15
2 9 20 16
NULL 19 20 15
GROUPING SETS 中還支持GROUP BY (),用於指定生成總計的空組。例如要查詢統計浙江地區各級別行政區個數,及總計個數,示例如下:
SELECT t.Level 級別,COUNT(1) 個數
FROM T_Districts t
WHERE SUBSTRING(t.Code,1,2) = '33'
GROUP BY GROUPING SETS(t.Level,());
查詢結果如下:
級別 個數
----------- -----------
1 1
2 6
3 10
NULL 17
4、GROUP BY 擴展函數
4.1、GROUPING 函數
GROUPING 函數用於指示當前行是否為聚合行,如果它返回 1 則表示聚合,相反,返回 0 則表示未聚合。僅當指定了 GROUP BY 時,GROUPING 才能在 SELECT 子句、HAVING 或 ORDER BY 子句中使用。
通常將一個分組欄位作為該函數的參數,然後通過判斷它的返回值來區分聚集行與常規行,從而進一步對結果集美化或過濾。示例如下:
SELECT GROUPING(t.Level) 標誌,t.Level 級別,COUNT(1) 個數
FROM T_Districts t
WHERE SUBSTRING(t.Code,1,2) = '33'
GROUP BY GROUPING SETS(t.Level,());
查詢結果如下:
標誌 級別 個數
---- ----------- -----------
0 1 1
0 2 6
0 3 10
1 NULL 17
4.2、GROUPING_ID 函數
GROUPING 函數用於計算分組級別,它將返回與行相關聯的 GROUPING 位向量對應的數值。GROUPING_ID 按從左到右的順序計算,如果是分組欄位,則為 0,如果是小計或合計則為 1,然後按欄位的順序將計算結果組成二進位序列(位向量),最後將位向量轉化為十進位數。僅當指定了 GROUP BY 時,GROUPING 才能在 SELECT 子句、HAVING 或 ORDER BY 子句中使用。
GROUPING_ID 函數在功能上等效於多個 GROUPING 函數,當查詢結果有多個聚合級別時,使用該函數會更容易表達行過濾條件。示例如下:
SELECT t.CourseId,t.StudentId,t.Counts,MAX(t.Scores) Scores,
GROUPING_ID(t.CourseId) gc,GROUPING_ID(t.StudentId) gs,GROUPING_ID(t.Counts) gt,
GROUPING_ID(t.CourseId,t.StudentId) gcs,
GROUPING_ID(t.StudentId,t.Counts) gst,
GROUPING_ID(t.CourseId,t.Counts) gct,
GROUPING_ID(t.CourseId,t.StudentId,t.Counts) gcst
FROM T_ExamResults t
WHERE t.StudentId = 1 AND t.Counts = 2
GROUP BY ROLLUP(t.CourseId,t.StudentId,t.Counts);
查詢結果如下:
CourseId StudentId Counts Scores gc gs gt gcs gst gct gcst
---------- ---------- --------- --------- ------- ------- ------- ------- ------- ------- -------
1 1 2 63.0 0 0 0 0 0 0 0
1 1 NULL 63.0 0 0 1 0 1 1 1
1 NULL NULL 63.0 0 1 1 1 3 1 3
2 1 2 98.0 0 0 0 0 0 0 0
2 1 NULL 98.0 0 0 1 0 1 1 1
2 NULL NULL 98.0 0 1 1 1 3 1 3
3 1 2 73.0 0 0 0 0 0 0 0
3 1 NULL 73.0 0 0 1 0 1 1 1
3 NULL NULL 73.0 0 1 1 1 3 1 3
NULL NULL NULL 98.0 1 1 1 3 3 3 7
5、本文小結
本文主要講述了 SQL Server 中分組查詢的相關知識點,包括 GROUP BY 與聚合函數、HAVING 聯合使用及 GROUP BY 的標準分組、擴展分組、擴展函數等。
另外,不知道會不會有讀者感到疑惑,為什麼擴展分組返回的結果集中經常會出現 NULL 值?其實這是 NULL 的一個特殊應用,它在 ROLLUP、CUBE 或 GROUPING SETS 操作的結果集內作為欄位的占位符,表示全體(數據)。
本文參考鏈接:
- 1、SQL Server 2016 GROUP BY
- 2、SQL Server 2016 HAVING
- 3、SQL Server 2016 GROUPING
- 4、SQL Server 2016 GROUPING_ID
去導航目錄篇下載創建本系列博文通用庫表及數據的 SQL 語句
本文鏈接:http://www.cnblogs.com/hanzongze/p/tsql-groupby.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!個人博客,能力有限,若有不當之處,敬請批評指正,謝謝!