SQL 對表進行分組(GROUP BY)


學習重點

  • 使用 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 的子句還沒有全部登場,已經出現的各子句的暫定順序如下所示。

▶ 子句的書寫順序(暫定)

  1. 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 BYWHERE 並用時,SELECT 語句的執行順序如下所示。

GROUP BYWHERE 並用時 SELECT 語句的執行順序

FROMWHEREGROUP BYSELECT

這與之前語法 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 子句中使用列的別名會引發錯誤

    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 子句)中能夠使用聚合函數。

專欄

DISTINCTGROUP BY

細心的讀者可能會發現,對表進行聚合查詢 中介紹的 DISTINCT 和本文介紹的 GROUP BY 子句,都能夠刪除后續列中的重復數據。例如,代碼清單 A 中的 2 條 SELECT 語句會返回相同的結果。

代碼清單 A DISTINCTGROUP 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 語句,那就太可惜了。

請參閱

(完)


  1. 不過,只有 MySQL 認同這種語法,所以能夠執行,不會發生錯誤(在多列候補中只要有一列滿足要求就可以了)。但是 MySQL 以外 的 DBMS 都不支持這樣的語法,因此請不要使用這樣的寫法。 ↩︎

  2. 需要注意的是,雖然這樣的寫法在 PostgreSQL 和 MySQL 都不會發生執行錯誤,但是這並不是通常的使用方法 ↩︎

  3. 它們都是數據的內部處理,都是通過排序處理來實現的。 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM