在實際SQL應用中,經常需要進行分組聚合,即將查詢對象按一定條件分組,然後對每一個組進行聚合分析。
8.3.1 GROUP BY子句創建分組
創建分組是通過GROUP BY子句實現的。與WHERE子句不同,GROUP BY子句用於歸納信息類型,以彙總相關數據。而爲什麼要使用GROUP BY子句創建分組呢?可通過下面這個簡單例子來說明。
實例17 單一分組的查詢
假如要從TEACHER表中查詢所有男教師的平均工資,用前面介紹的聚合函數AVG(),實現代碼如下:
SELECT AVG(SAL) AS boyavg_sal
FROM TEACHER
WHERE TSEX='男'
運行結果如圖8.18所示。
圖8.18 TEACHER表中查詢所有男教師的平均工資
而如果同時需要查詢所有女教師的平均工資,該如何處理呢?顯然,採用上述方法只能在WHERE子句中改變查詢條件,重新查詢。而如果要在一次查詢中,同時得到二者的查詢結果,就需要以性別爲基準,將表中的所有數據記錄分組,即男教師組和女教師組,並分別對兩組數據進行分析,即計算工資(SAL列)的平均值。
實現上述功能,就需要使用分組子句GROUP BY。包括GROUP BY子句的查詢就稱爲組合查詢。語法如下。
SELECT column, SUM(column)
FROM table
GROUP BY column
說明:GROUP BY子句依據column列裏的數據對行進行分組,即具有相同的值的行被劃爲一組。它一般與聚合函數同時使用。當然,這裏的SUM()函數也可以是其他聚合函數。所有的組合列(GROUP BY子句中列出的列)必須是來自FROM子句列出的表,不能根據實際值、聚合函數結果或者其他表達式計算的值來對行分組。
實例18 GROUP BY子句分組查詢
從TEACHER表中查詢所有男教師的平均工資和所有女教師的平均工資,實現代碼如下。
SELECT TSEX+'教師'AS TEACHER, AVG(SAL) AS avg_sal
FROM TEACHER
GROUP BY TSEX
運行結果如圖8.19所示。
圖8.19 TEACHER表中所有男教師和所有女教師的平均工資
下面分析一下DBMS執行該實例的步驟。
DBMS首先執行FROM子句,將表TEACHER作爲中間表。
如果有WHERE子句,則根據其中的搜索條件,從中間表中去除那些值爲False的列。這裏沒有WHERE子句,所以跳過該步。
根據GROUP BY子句指定的分組列即TSEX,將中間表中的數據進行分組。這裏TSEX只有“男”和“女”,因此中間表中的數據被分成了兩組,一組中TSEX的值爲“男”,另一組中TSEX的值爲“女”。
爲每個行組計算SELECT子句中的值,併爲每組生成查詢結果中的一行。對於TSEX值爲“男”的行組,SELECT子句中首先執行“TSEX+'教師'”,得到“男教師”列值,再執行“AVG(SAL)”,求得該行組中的SAL的均值,將這兩個值作爲結果表中的一條記錄。同樣,對TSEX值爲“女”的行組,進行類似的操作得到另一條記錄。
8.3.2 GROUP BY子句根據多列組合行
上節介紹的GROUP BY子句進行組合查詢,在GROUP BY子句中只有一列,它是組合查詢的最簡單形式。如果表中的行組依賴於多列,只要在查詢的GROUP BY子句中,列出定義組所需的所有列即可。
實例19 GROUP BY子句根據多列組合行
從TEACHER表中查詢各個系男教師和女教師的人數。實現代碼:
SELECT DNAME,TSEX, COUNT(*) AS TOTAL_NUM
FROM TEACHER
GROUP BY DNAME,TSEX
ORDER BY DNAME
運行結果如圖8.20所示。
圖8.20 TEACHER表中各系男教師和女教師的人數
從結果中可以發現,只有計算機系列出了男教師和女教師的人數。而別的系,只列出了一個值,這是因爲,在TEACHER表中,這些系中的教師只有一種性別,如生物系只有兩個女教師,而沒有男教師,系統就認爲該行記錄爲NULL,所以生物系的男教師的人數記錄就不包含在結果表中。
8.3.3 ROLLUP運算符和CUBE運算符
在使用GROUP BY子句根據多列組合行時,可以在GROUP BY子句中使用ROLLUP運算符和CUBE運算符,擴展查詢結果。兩者的主要不同在於,CUBE運算符擴展的信息要比ROLLUP運算符多,下面結合具體的實例講解二者的使用及區別。
1.ROLLUP運算符的使用
實例20 使用ROLLUP運算符擴查詢
使用ROLLUP運算符擴展實例19查詢結果。實現代碼:
SELECT DNAME,TSEX, COUNT(*) AS TOTAL_NUM
FROM TEACHER
GROUP BY DNAME,TSEX WITH ROLLUP
ORDER BY DNAME
運行結果如圖8.21所示。
圖8.21 ROLLUP運算符擴展的組合查詢結果
與實例19相比,增加了7行數據。其中一行(結果中的第1行)爲TEACHER表中所有教師的總人數,另外還分別爲各系(DNAME)分組增加了一行(結果中的第3、5、8、10、12、14行),統計了各系教師的總人數。
實例21 改變GROUP BY子句中列的排列順序對ROLLUP運算符的影響
如果改變GROUP BY子句中列的排列順序,使用ROLLUP運算符會得到不同的結果,如下面的代碼:
SELECT DNAME,TSEX, COUNT(*) AS TOTAL_NUM
FROM TEACHER
GROUP BY TSEX, DNAME WITH ROLLUP
ORDER BY DNAME
運行結果如圖8.22所示。
圖8.22 依據系名排序後的結果
與8.3.2節實例相比,結果集中增加了3行記錄,其中一行(結果中的第3行)爲TEACHER表中所有教師的總人數,而另外兩行(結果中的第1行和第2行)爲性別(TSEX)分組的人數統計,即所有男教師的數量和所有女教師的數量。
2.CUBE運算符的使用
實例22 使用CUBE運算符擴展查詢
使用CUBE運算符擴展實例19查詢結果。實現代碼:
SELECT DNAME,TSEX, COUNT(*) AS TOTAL_NUM
FROM TEACHER
GROUP BY DNAME,TSEX WITH CUBE
ORDER BY DNAME
運行結果如圖8.23所示。
圖8.23 使用CUBE運算符擴展的組合查詢結果
從結果中可以發現,通過使用CUBE運算符,結果集中除了包含多列組合(DNAME和TSEX)的統計結果外,還包含了整表(TEACHER表)的統計結果和各單列(DNAME、TSEX)的統計結果。
8.3.4 GROUP BY子句中的NULL值處理
當GROUP BY子句中用於分組的列中出現NULL值時,將如何分組呢?按照前面的介紹,NULL不等於NULL(在WHERE子句中有過介紹)。然而,在GROUP BY子句中,卻將所有的NULL值分在同一組,即認爲它們是“相等”的。
實例23 GROUP BY子句中的NULL值處理
從TEACHER表中查詢所有的工資數及各工資的人數。實現代碼:
SELECT SAL,COUNT(*) AS TOTAL_NUM
FROM TEACHER
GROUP BY SAL
ORDER BY SAL
運行結果如圖8.24所示。
圖8.24 TEACHER表中所有的工資數及各工資的人數
可見,SAL列中的兩行NULL值被歸爲了一組。
8.3.5 HAVING子句
GROUP BY子句分組,只是簡單地依據所選列的數據進行分組,將該列具有相同值的行劃爲一組。而實際應用中,往往還需要刪除那些不能滿足條件的行組,爲了實現這個功能,SQL提供了HAVING子句。語法如下。
SELECT column, SUM(column)
FROM table
GROUP BY column
HAVING SUM(column) condition value
說明:HAVING通常與GROUP BY子句同時使用。當然,語法中的SUM()函數也可以是其他任何聚合函數。DBMS將HAVING子句中的搜索條件應用於GROUP BY子句產生的行組,如果行組不滿足搜索條件,就將其從結果表中刪除。
注意 |
前面介紹的有關WHERE子句的所有操作,如使用連接符、通配符、函數等,在HAVING子句中都可以使用。 |
實例24 HAVING子句的應用
從TEACHER表中查詢至少有兩位教師的系及教師人數。實現代碼:
SELECT DNAME, COUNT(*) AS num_teacher
FROM TEACHER
GROUP BY DNAME
HAVING COUNT(*)>=2
運行結果如圖8.25所示。
圖8.25 TEACHER表中至少有兩位教師的系及教師人數
8.3.6 HAVING子句與WHERE子句
HAVING子句和WHERE子句的相似之處在於,它也定義搜索條件。但與WHERE子句不同,HAVING子句與組有關,而不是與單個的行有關。
如果指定了GROUP BY子句,那麼HAVING子句定義的搜索條件將作用於這個GROUP BY子句創建的那些組。
如果指定WHERE子句,而沒有指定GROUP BY子句,那麼HAVING子句定義的搜索條件將作用於WHERE子句的輸出,並把這個輸出看作是一個組。
如果既沒有指定GROUP BY子句也沒有指定WHERE子句,那麼HAVING子句定義的搜索條件將作用於FROM子句的輸出,並把這個輸出看作是一個組。
在SELECT語句中,WHERE和HAVING子句的執行順序不同。在本書的5.1.2節介紹的SELECT語句的執行步驟可知,WHERE子句只能接收來自FROM子句的輸入,而HAVING子句則可以接收來自GROUP BY子句、WHERE子句和FROM子句的輸入。
下面通過幾個實例講解HAVING子句和WHERE子句的不同作用。
實例25 HAVING子句和WHERE子句的不同作用
從TEACHER表中查詢有女教師的系及擁有的女教師數量。實現代碼:
SELECT DNAME, COUNT(TSEX) AS num_girl
FROM TEACHER
WHERE TSEX='女'
GROUP BY DNAME
運行結果如圖8.26所示。
圖8.26 TEACHER表中具有女教師的系及擁有的女教師數量
可見得到了3個系,與TEACHER表中數據相吻合。如果在上例中不使用WHERE子句,而是使用HAVING子句,教師限制爲女教師,如下面的代碼:
SELECT DNAME, COUNT(TSEX) AS num_girl
FROM TEACHER
GROUP BY DNAME
HAVING TSEX='女'
執行該代碼,系統會給出以下出錯提示信息。
Column 'TEACHER.TSEX' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause.
不能把單個的TSEX的值應用於組,包括在HAVING子句中的列必須是組列。因此,在這種情況下,WHERE子句就不可能用HAVING子句代替。
在數據的分組聚合分析中,HAVING子句與WHERE子句也可以共存。WHERE子句在分組之前過濾數據,而HAVING子句則過濾分組後的數據。
實例26 HAVING子句與WHERE子句聯合使用
查詢至少有兩名女教師的系及擁有的女教師數量。實現代碼:
SELECT DNAME, COUNT(TSEX) AS num_girl
FROM TEACHER
WHERE TSEX='女'
GROUP BY DNAME
HAVING COUNT(TSEX)>=2
運行結果如圖8.27所示。
圖8.27 TEACHER表中至少有兩名女教師的系及擁有的女教師數量
這裏通過HAVING子句對分組結果進行搜索,去除了不滿足搜索條件(即只有一個教師的經濟管理系)的行。
通常情況下,HAVING子句都與GROUP BY子句一起使用,這樣就可以聚合相關數據,然後篩選這些數據,以進一步細化搜索。然而,如果沒有GROUP BY子句,HAVING子句也可以單獨使用。
實例27 HAVING子句的單獨使用
如下面的代碼:
SELECT COUNT(TSEX) AS num_girl
FROM TEACHER
WHERE TSEX='女'
HAVING COUNT(TSEX)>4
運行結果如圖8.28所示。
圖8.28 單獨使用HAVING子句的查詢結果
上述代碼實現的功能實際上是從教師表中查詢所有女教師的數量,如果女教師的數量大於4,則將其作爲查詢結果,而如果數量少於或者等於4,那麼查詢結果將爲空值。當然,這種不使用GROUP BY子句而使用HAVING子句的情況,在實際應用中很少用到。
8.3.7 SELECT語句各查詢子句總結
至此,SELECT語句中的所有子句都介紹完了,它們在SELECT查詢語句中的排列順序及主要作用如表8-2所示。
表8-2 SELECT查詢語句及其所有子句
順 序 號 |
子句關鍵詞 |
子 句 功 能 |
1 |
SELECT |
從指定表中取出指定的列的數據 |
2 |
FROM |
指定要查詢操作的表 |
3 |
WHERE |
用來規定一種選擇查詢的標準 |
4 |
GROUP BY |
對結果集進行分組,常與聚合函數一起使用 |
5 |
HAVING |
返回選取的結果集中行的數目 |
6 |
ORDER BY |
指定分組的搜尋條件 |
如果在同一個SELECT查詢語句中,用到了表8-2所示的一些查詢子句,則各查詢子句的排列就依照它們的順序號由低到高的順序。因此,完整的SELECT查詢語句可以表示爲:
SELECT select_list
FROM table_source
[ WHERE search_condition ]
[ GROUP BY group_by_expression ]
[ HAVING search_condition ]
[ ORDER BY order_expression [ ASC | DESC ] ]
其中[ ]中的部分爲可選項。
實例28 在SELECT語句中綜合使用查詢子句
從TEACHER表中查詢至少有兩名女教師的系及擁有的女教師數量,並按女教師的數量升序的順序排列結果。實現代碼:
SELECT DNAME, COUNT(TSEX) AS num_girl
FROM TEACHER
WHERE TSEX='女'
GROUP BY DNAME
HAVING COUNT(TSEX)>=2
ORDER BY num_girl
運行結果如圖8.29所示。