學習重點
使用
GROUP BY
子句可以像切蛋糕那樣將表分割。通過使用聚合函數和GROUP BY
子句,可以根據“商品種類”或者“登記日期”等將表分割后再進行匯總。聚合鍵中包含
NULL
時,在結果中會以“不確定”行(空行)的形式表現出來。使用聚合函數和
GROUP BY
子句時需要注意以下 4 點。① 只能寫在
SELECT
子句之中②
GROUP BY
子句中不能使用SELECT
子句中列的別名③
GROUP BY
子句的聚合結果是無序的④
WHERE
子句中不能使用聚合函數
一、GROUP BY
子句
目前為止,我們看到的聚合函數的使用方法,無論是否包含 NULL
,無論是否刪除重復數據,都是針對表中的所有數據進行的匯總處理。下面,我們先把表分成幾組,然后再進行匯總處理。也就是按照“商品種類”“登記日期”等進行匯總。
這里我們將要第一次接觸到 GROUP BY
子句,其語法結構如下所示。
KEYWORD
GROUP BY
子句
語法 1 使用 GROUP BY
子句進行匯總
SELECT <列名1>, <列名2>, <列名3>, ……
FROM <表名>
GROUP BY <列名1>, <列名2>, <列名3>, ……;
下面我們就按照商品種類來統計一下數據行數(= 商品數量)(代碼清單 13)。
代碼清單 13 按照商品種類統計數據行數
SELECT product_type, COUNT(*)
FROM Product
GROUP BY product_type;
執行結果
product_type | count
--------------+------
衣服 | 2
辦公用品 | 2
廚房用具 | 4
如上所示,未使用 GROUP BY
子句時,結果只有 1 行,而這次的結果卻是多行。這是因為不使用 GROUP BY
子句時,是將表中的所有數據作為一組來對待的。而使用 GROUP BY
子句時,會將表中的數據分為多個組進行處理。如圖 4 所示,GROUP BY
子句對表進行了切分。

圖 4 按照商品種類對表進行切分
這樣,GROUP BY
子句就像切蛋糕那樣將表進行了分組。在 GROUP BY
子句中指定的列稱為聚合鍵或者分組列。由於能夠決定表的切分方式,所以是非常重要的列。當然,GROUP BY
子句也和 SELECT
子句一樣,可以通過逗號分隔指定多列。
KEYWORD
聚合鍵
分組列
如果用畫線的方式來切分表中數據的話,就會得到圖 5 那樣以商品種類為界線的三組數據。然后再計算每種商品的數據行數,就能得到相應的結果了。

圖 5 按照商品種類對表進行切分
法則 6
GROUP BY
就像是切分表的一把刀。
此外,GROUP BY
子句的書寫位置也有嚴格要求,一定要寫在 FROM
語句之后(如果有 WHERE
子句的話需要寫在 WHERE
子句之后)。如果無視子句的書寫順序,SQL 就一定會無法正常執行而出錯。目前 SQL 的子句還沒有全部登場,已經出現的各子句的暫定順序如下所示。
▶ 子句的書寫順序(暫定)
SELECT
→ 2.FROM
→ 3.WHERE
→ 4.GROUP BY
法則 7
SQL 子句的順序不能改變,也不能互相替換。
二、聚合鍵中包含 NULL
的情況
接下來我們將進貨單價(purchase_price
)作為聚合鍵對表進行切分。在 GROUP BY
子句中指定進貨單價的結果請參見代碼清單 14。
代碼清單 14 按照進貨單價統計數據行數
SELECT purchase_price, COUNT(*)
FROM Product
GROUP BY purchase_price;
上述 SELECT
語句的結果如下所示。
執行結果
像 790 日元或者 500 日元這樣進貨單價很清楚的數據行不會有什么問題,結果與之前的情況相同。問題是結果中的第一行,也就是進貨單價為 NULL
的組。從結果我們可以看出,當聚合鍵中包含 NULL
時,也會將 NULL
作為一組特定的數據,如圖 6 所示。

圖 6 按照進貨單價對表進行切分
這里的 NULL
,大家可以理解為“不確定”。
法則 8
聚合鍵中包含
NULL
時,在結果中會以“不確定”行(空行)的形式表現出來。
三、使用 WHERE
子句時 GROUP BY
的執行結果
在使用了 GROUP BY
子句的 SELECT
語句中,也可以正常使用 WHERE
子句。子句的排列順序如前所述,語法結果如下所示。
語法 2 使用 WHERE
子句和 GROUP BY
子句進行匯總處理
SELECT <列名1>, <列名2>, <列名3>, ……
FROM <表名>
WHERE
GROUP BY <列名1>, <列名2>, <列名3>, ……;
像這樣使用 WHERE
子句進行匯總處理時,會先根據 WHERE
子句指定的條件進行過濾,然后再進行匯總處理。請看代碼清單 15。
代碼清單 15 同時使用 WHERE
子句和 GROUP BY
子句
SELECT purchase_price, COUNT(*)
FROM Product
WHERE product_type = '衣服'
GROUP BY purchase_price;
因為上述 SELECT
語句首先使用了 WHERE
子句對記錄進行過濾,所以實際上作為聚合對象的記錄只有 2 行,如表 1 所示。
表 1 WHERE
子句過濾的結果
product_type (商品種類) |
product_name (商品名稱) |
product_id (商品編號) |
sale_price (銷售單價) |
purchase_price (進貨單價) |
regist_date (登記日期) |
---|---|---|---|---|---|
衣服 | T 恤衫 | 0001 | 1000 | 500 | 2009-09-20 |
衣服 | 運動 T 恤 | 0003 | 4000 | 2800 |
使用進貨單價對這 2 條記錄進行分組,就得到了如下的執行結果。
執行結果
purchase_price | count
----------------+------
500 | 1
2800 | 1
GROUP BY
和 WHERE
並用時,SELECT
語句的執行順序如下所示。
▶ GROUP BY
和 WHERE
並用時 SELECT
語句的執行順序
FROM
→ WHERE
→ GROUP BY
→ SELECT
這與之前語法 2 中的說明順序有些不同,這是由於在 SQL 語句中,書寫順序和 DBMS 內部的執行順序並不相同。這也是 SQL 難以理解的原因之一。
四、與聚合函數和 GROUP BY
子句有關的常見錯誤
截至目前,我們已經學習了 聚合函數 和 GROUP BY
子句的基本使用方法。雖然由於使用方便而經常被使用,但是書寫 SQL 時卻很容易出錯,希望大家特別小心。
-
常見錯誤 ① ——在
SELECT
子句中書寫了多余的列在使用
COUNT
這樣的聚合函數時,SELECT
子句中的元素有嚴格的限制。實際上,使用聚合函數時,SELECT
子句中只能存在以下三種元素。-
常數
-
聚合函數
-
GROUP BY
子句中指定的列名(也就是聚合鍵)
在 SQL 概要 中我們介紹過,常數就是像數字
123
,或者字符串'測試'
這樣寫在 SQL 語句中的固定值,將常數直接寫在SELECT
子句中沒有任何問題。此外還可以書寫聚合函數或者聚合鍵,這些在之前的示例代碼中都已經出現過了。這里經常會出現的錯誤就是把聚合鍵之外的列名書寫在
SELECT
子句之中。例如代碼清單 16 中的SELECT
語句就會發生錯誤,無法正常執行。代碼清單 16 在
SELECT
子句中書寫聚合鍵之外的列名會發生錯誤SELECT product_name, purchase_price, COUNT(*) FROM Product GROUP BY purchase_price;
執行結果(使用 PostgreSQL 的情況)
ERROR:列"product,product_name"必須包含在GROUP BY子句之中,或者必須在聚合函數內使用 行 1: SELECT product_name, purchase_price, COUNT(*)
列名
product_name
並沒有包含在GROUP BY
子句當中。因此,該列名也不能書寫在SELECT
子句之中 [1]。不支持這種語法的原因,大家仔細想一想應該就明白了。通過某個聚合鍵將表分組之后,結果中的一行數據就代表一組。例如,使用進貨單價將表進行分組之后,一行就代表了一個進貨單價。問題就出在這里,聚合鍵和商品名並不一定是一對一的。
例如,進貨單價是 2800 日元的商品有“運動 T 恤”和“菜刀”兩種,但是 2800 日元這一行應該對應哪個商品名呢(圖 7)?如果規定了哪種商品優先表示的話則另當別論,但其實並沒有這樣的規則。
圖 7 聚合鍵和商品名不是一對一的情況
像這樣與聚合鍵相對應的、同時存在多個值的列出現在
SELECT
子句中的情況,理論上是不可能的。法則 9
使用
GROUP BY
子句時,SELECT
子句中不能出現聚合鍵之外的列名。 -
-
常見錯誤 ② ——在
GROUP BY
子句中寫了列的別名這也是一個非常常見的錯誤。在 SELECT 語句基礎 中我們學過,
SELECT
子句中的項目可以通過AS
關鍵字來指定別名。但是,在GROUP BY
子句中是不能使用別名的。代碼清單 17 中的SELECT
語句會發生錯誤 [2]。代碼清單 17
GROUP BY
子句中使用列的別名會引發錯誤上述語句發生錯誤的原因之前已經介紹過了,是 SQL 語句在 DBMS 內部的執行順序造成的——
SELECT
子句在GROUP BY
子句之后執行。在執行GROUP BY
子句時,SELECT
子句中定義的別名,DBMS 還並不知道。使用本教程提供的 PostgreSQL 執行上述 SQL 語句並不會發生錯誤,而會得到如下結果。但是這樣的寫法在其他 DBMS 中並不是通用的,因此請大家不要使用。
執行結果(使用 PostgreSQL 的情況)
pt | count -------------+------ 衣服 | 2 辦公用品 | 2 廚房用具 | 4
法則 10
在
GROUP BY
子句中不能使用SELECT
子句中定義的別名。 -
常見錯誤 ③ ——
GROUP BY
子句的結果能排序嗎GROUP BY
子句的結果通常都包含多行,有時可能還會是成百上千行。那么,這些結果究竟是按照什么順序排列的呢?答案是:“隨機的。”
我們完全不知道結果記錄是按照什么規則進行排序的。可能乍一看是按照行數的降序或者聚合鍵的升序進行排列的,但其實這些全都是偶然的。當你再次執行同樣的
SELECT
語句時,得到的結果可能會按照完全不同的順序進行排列。通常
SELECT
語句的執行結果的顯示順序都是隨機的,因此想要按照某種特定順序進行排序的話,需要在SELECT
語句中進行指定。具體的方法將在 對查詢結果進行排序 中學習。KEYWORD
- 排序
法則 11
GROUP BY
子句結果的顯示是無序的。 -
常見錯誤 ④ ——在
WHERE
子句中使用聚合函數最后要介紹的是初學者非常容易犯的一個錯誤。我們還是先來看一下之前提到的按照商品種類(
product_type
列)對表進行分組,計算每種商品數據行數的例子吧。SELECT
語句如代碼清單 18 所示。代碼清單 18 按照商品種類統計數據行數
SELECT product_type, COUNT(*) FROM Product GROUP BY product_type;
執行結果
product_type | count --------------+------- 衣服 | 2 辦公用品 | 2 廚房用具 | 4
如果我們想要取出恰好包含 2 行數據的組該怎么辦呢?滿足要求的是“辦公用品”和“衣服”。
想要指定選擇條件時就要用到
WHERE
子句,初學者通常會想到使用代碼清單 19 中的SELECT
語句吧。代碼清單 19 在
WHERE
子句中使用聚合函數會引發錯誤SELECT product_type, COUNT(*) FROM Product WHERE COUNT(*) = 2 GROUP BY product_type;
遺憾的是,這樣的
SELECT
語句在執行時會發生錯誤。執行結果(使用 PostgreSQL 的情況)
ERROR: 不能在WHERE子句中使用聚合 行 3: WHERE COUNT(*) = 2
實際上,只有
SELECT
子句和HAVING
子句(以及之后將要學到的ORDER BY
子句)中能夠使用COUNT
等聚合函數。並且,HAVING
子句可以非常方便地實現上述要求。為聚合結果指定條件 我們將會學習 HAVING 子句。法則 12
只有
SELECT
子句和HAVING
子句(以及ORDER BY
子句)中能夠使用聚合函數。
專欄
DISTINCT
和GROUP BY
細心的讀者可能會發現,對表進行聚合查詢 中介紹的
DISTINCT
和本文介紹的GROUP BY
子句,都能夠刪除后續列中的重復數據。例如,代碼清單 A 中的 2 條SELECT
語句會返回相同的結果。代碼清單 A
DISTINCT
和GROUP BY
能夠實現相同的功能SELECT DISTINCT product_type FROM Product; SELECT product_type FROM Product GROUP BY product_type;
執行結果
product_type -------------- 衣服 辦公用品 廚房用具
除此之外,它們還都會把
NULL
作為一個獨立的結果返回,對多列使用時也會得到完全相同的結果。其實不僅處理結果相同,執行速度也基本上差不多 [3],那么到底應該使用哪一個呢?但其實這個問題本身就是本末倒置的,我們應該考慮的是該
SELECT
語句是否滿足需求。選擇的標准其實非常簡單,在“想要刪除選擇結果中的重復記錄”時使用DISTINCT
,在“想要計算匯總結果”時使用GROUP BY
。不使用
COUNT
等聚合函數,而只使用GROUP BY
子句的SELECT
語句,會讓人覺得非常奇怪,使人產生“到底為什么要對表進行分組呢?這樣做有必要嗎?”等疑問。SQL 語句的語法與英語十分相似,理解起來非常容易,如果大家浪費了這一優勢,編寫出一些難以理解的 SQL 語句,那就太可惜了。
請參閱
(完)