1. 程式人生 > >SQL Server溫故系列(5):SQL 查詢之分組查詢 GROUP BY

SQL Server溫故系列(5):SQL 查詢之分組查詢 GROUP BY

  • 1、GROUP BY 與聚合函式
  • 2、GROUP BY 與 HAVING
  • 3、GROUP BY 擴充套件分組
    • 3.1、GROUP BY ROLLUP
    • 3.2、GROUP BY CUBE
    • 3.3、GROUP BY GROUPING SETS
  • 4、GROUP BY 擴充套件函式
    • 4.1、GROUPING 函式
    • 4.2、GROUPING_ID 函式
  • 5、本文小結

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 AGROUP 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
版權宣告:本文為部落格園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文連結!個人部落格,能力有限,若有不當之處,敬請批評指正,謝謝!