前面學過聚合運算,可以把整張表的資料「壓縮」成一筆。
但很多時候,你不只想知道「全部加起來是多少」。
比如說,老闆問你:「每個部門有幾個員工?」
這時候就需要用 GROUP BY 來「分組」。
什麼是分組?
分組就是把資料按照某個「類別」分成一堆一堆。
比如說,這個世界上的人可以按照:
- 國籍分:台灣人、日本人、美國人⋯⋯
- 血型分:A 型、B 型、O 型、AB 型
同一個類別的人會被歸到同一堆。
在資料庫裡,這個「類別」就是欄位。
比如說,員工表裡有一個「部門」欄位,值可能是「行銷」、「工程」、「人資」。
如果你想把員工按照部門分成一堆一堆,語法就是:
GROUP BY 部門這樣行銷部的員工會被歸成一堆、工程部的員工一堆、人資部的員工一堆。
用 GROUP BY 找出不重複的資料
了解分組的概念之後,來看一個實際的例子。
假設我們有一張訂單表:
| 訂單編號 | 會員 | 金額 |
|---|---|---|
| 1 | Alice | 100 |
| 2 | Alice | 200 |
| 3 | Bob | 150 |
| 4 | Alice | 300 |
| 5 | Bob | 250 |
老闆說:「幫我找出有下過單的會員有哪些。」
你可能會這樣寫:
SELECT 會員
FROM 訂單結果會是:
| 會員 |
|---|
| Alice |
| Alice |
| Bob |
| Alice |
| Bob |
但這樣會有問題:Alice 出現了 3 次,Bob 出現了 2 次。
老闆要的是「不重複的會員名單」,不是一堆重複的名字。
怎麼做到「不重複」?
用 GROUP BY:
SELECT 會員
FROM 訂單
GROUP BY 會員為什麼這樣可以做到不重複?
因為 GROUP BY 會員,會把「會員欄位值相同的」放在一起:
| 組別 | 訂單編號 | 會員 | 金額 |
|---|---|---|---|
| 第一組 | 1 | Alice | 100 |
| 第一組 | 2 | Alice | 200 |
| 第一組 | 4 | Alice | 300 |
| 第二組 | 3 | Bob | 150 |
| 第二組 | 5 | Bob | 250 |
Alice 的 3 筆變成一組、Bob 的 2 筆變成一組。
然後把每一組「壓縮」成一列。
因為我們寫的是 GROUP BY 會員,所以資料庫會用「會員」這個欄位來壓縮。
看看第一組,有 3 筆訂單:
| 訂單編號 | 會員 | 金額 |
|---|---|---|
| 1 | Alice | 100 |
| 2 | Alice | 200 |
| 4 | Alice | 300 |
壓縮成一列之後,會變成什麼?
- 會員欄位:這是 GROUP BY 指定的欄位,3 筆都是 Alice,壓縮後輸出 Alice。
- 訂單編號欄位:不是 GROUP BY 指定的欄位,3 筆分別是 1、2、4,壓縮成一列要填什麼?1?2?4?還是加起來變成 7?資料庫不知道你要什麼,所以不讓你直接用。
- 金額欄位:同樣不是 GROUP BY 指定的欄位,資料庫也不讓你直接用。
所以壓縮之後,只有 GROUP BY 指定的欄位可以直接用,其他欄位沒辦法直接 SELECT。
結果就是:
| 會員 |
|---|
| Alice |
| Bob |
這是 GROUP BY 壓縮後的結果。
接下來輪到 SELECT 上場。
SELECT 會員,就是告訴資料庫:「從壓縮後的結果中,把會員欄位顯示出來。」
因為壓縮後只剩會員欄位可以用,所以 SELECT 也只能選會員:
| 會員 |
|---|
| Alice |
| Bob |
如果你硬要 SELECT 訂單編號或金額,資料庫會報錯,因為這些欄位在壓縮後已經不存在了。
這就是 GROUP BY 在做的事:先分組,再壓縮。
GROUP BY 之後,看不到個別資料
老闆又說:「那順便把每筆訂單的金額也列出來。」
你可能會這樣寫:
SELECT 會員, 金額
FROM 訂單
GROUP BY 會員但這樣會報錯。
為什麼?
前面說過,GROUP BY 會員 會把資料壓縮,壓縮後只剩「會員」欄位可以用。
金額欄位在壓縮的過程中已經消失了,所以沒辦法直接 SELECT。
規則:SELECT 的欄位必須出現在 GROUP BY 裡
這是一個很重要的規則:
如果你用了 GROUP BY,那 SELECT 裡面的欄位,必須出現在 GROUP BY 裡面。
為什麼?
因為 GROUP BY 會把資料壓縮,壓縮後只剩下 GROUP BY 指定的欄位還存在。
其他欄位在壓縮過程中消失了,沒辦法直接 SELECT。
用多個欄位分組
老闆說:「那我想看每個會員用過哪些物流方式。」
假設訂單表多了一個「物流方式」欄位:
| 訂單編號 | 會員 | 金額 | 物流方式 |
|---|---|---|---|
| 1 | Alice | 100 | 宅配 |
| 2 | Alice | 200 | 超商取貨 |
| 3 | Bob | 150 | 宅配 |
| 4 | Alice | 300 | 宅配 |
| 5 | Bob | 250 | 宅配 |
這時候你需要用「會員 + 物流方式」的組合來分組:
SELECT 會員, 物流方式
FROM 訂單
GROUP BY 會員, 物流方式GROUP BY 會員, 物流方式 會把「會員和物流方式都相同」的放在一起:
| 組別 | 訂單編號 | 會員 | 金額 | 物流方式 |
|---|---|---|---|---|
| 第一組 | 1 | Alice | 100 | 宅配 |
| 第一組 | 4 | Alice | 300 | 宅配 |
| 第二組 | 2 | Alice | 200 | 超商取貨 |
| 第三組 | 3 | Bob | 150 | 宅配 |
| 第三組 | 5 | Bob | 250 | 宅配 |
Alice 宅配有 2 筆,變成一組。
Alice 超商取貨有 1 筆,自己一組。
Bob 宅配有 2 筆,變成一組。
然後把每一組壓縮成一列。
壓縮的時候,資料庫會看 GROUP BY 會員, 物流方式,知道要保留「會員」和「物流方式」這兩個欄位。
其他欄位(訂單編號、金額)在壓縮過程中消失。
結果是:
| 會員 | 物流方式 |
|---|---|
| Alice | 宅配 |
| Alice | 超商取貨 |
| Bob | 宅配 |
這是 GROUP BY 壓縮後的結果。
最後 SELECT 會員, 物流方式,就是從這個結果中取出這兩個欄位顯示出來:
| 會員 | 物流方式 |
|---|---|
| Alice | 宅配 |
| Alice | 超商取貨 |
| Bob | 宅配 |
這就是所有「會員 + 物流方式」的不重複組合。
搭配 WHERE 和 ORDER BY
範例:篩選成功訂單並排序
老闆說:「幫我把訂單狀態是『成功』的挑出來,然後按照物流方式排序。」
假設訂單表多了一個「訂單狀態」欄位:
| 訂單編號 | 會員 | 金額 | 物流方式 | 訂單狀態 |
|---|---|---|---|---|
| 1 | Alice | 100 | 宅配 | 成功 |
| 2 | Alice | 200 | 超商取貨 | 成功 |
| 3 | Bob | 150 | 宅配 | 失敗 |
| 4 | Alice | 300 | 宅配 | 成功 |
| 5 | Bob | 250 | 宅配 | 成功 |
這時候你需要結合 WHERE、GROUP BY、ORDER BY:
SELECT 會員, 物流方式
FROM 訂單
WHERE 訂單狀態 = '成功'
GROUP BY 會員, 物流方式
ORDER BY 物流方式SQL 的執行順序
這段 SQL 是怎麼執行的?
第一步:FROM 訂單
先找到訂單表,拿到全部 5 筆資料。
第二步:WHERE 訂單狀態 = ‘成功’
篩選出訂單狀態是「成功」的資料,剩下 4 筆:
| 訂單編號 | 會員 | 金額 | 物流方式 | 訂單狀態 |
|---|---|---|---|---|
| 1 | Alice | 100 | 宅配 | 成功 |
| 2 | Alice | 200 | 超商取貨 | 成功 |
| 4 | Alice | 300 | 宅配 | 成功 |
| 5 | Bob | 250 | 宅配 | 成功 |
第三步:GROUP BY 會員, 物流方式
把「會員和物流方式都相同」的放在一起,然後壓縮:
| 會員 | 物流方式 |
|---|---|
| Alice | 宅配 |
| Alice | 超商取貨 |
| Bob | 宅配 |
第四步:SELECT 會員, 物流方式
從壓縮後的結果取出這兩個欄位顯示。
第五步:ORDER BY 物流方式
按照物流方式排序,結果是:
| 會員 | 物流方式 |
|---|---|
| Alice | 宅配 |
| Bob | 宅配 |
| Alice | 超商取貨 |
GROUP BY 的執行順序
前面的範例展示了 SQL 的執行順序,這裡整理成表格方便查閱:
| 順序 | 指令 | 說明 |
|---|---|---|
| 1 | FROM | 先找到資料來源 |
| 2 | WHERE | 篩選出符合條件的資料 |
| 3 | GROUP BY | 把資料分組並壓縮 |
| 4 | SELECT | 從壓縮後的結果取出欄位 |
| 5 | ORDER BY | 排序 |
為什麼 WHERE 在 GROUP BY 之前?
因為要先篩選資料,再進行分組。
如果先分組再篩選,可能會發現某些已經分好組的資料其實應該被排除,這樣就白做工了。
為什麼 GROUP BY 在 SELECT 之前?
因為要先分組並壓縮,SELECT 才知道有哪些欄位可以用。
如果還沒分組,就無法確定哪些欄位在壓縮後還存在。
GROUP BY 不能用 SELECT 的別名
因為 GROUP BY 在 SELECT 之前執行,所以不能在 GROUP BY 裡使用 SELECT 定義的別名。
-- 這樣寫理論上會報錯
SELECT 會員 AS member
FROM 訂單
GROUP BY member有些資料庫(例如 PostgreSQL)允許這樣寫,但建議不要這樣做,原因是:
- 這違反了執行順序的邏輯
- 很多資料庫不支援這種寫法
如果需要在不同的資料庫之間切換,避免使用這種寫法會比較安全。
小結
這篇文章介紹了 GROUP BY 分組:
- 分組的概念:按照欄位把資料分成一堆一堆
- 找出不重複資料:GROUP BY 會把重複的資料揉成一團
- 看不到個別資料:分組後只能看到分組依據和聚合結果
- SELECT 欄位的規則:必須出現在 GROUP BY 或被聚合函數包起來
- 多欄位分組:可以用多個欄位的組合來分組
- 資料會變少:分組後資料列通常會變少
- 執行順序:FROM → WHERE → GROUP BY → SELECT → ORDER BY
下一篇會學習如何在分組之後再做篩選,也就是 HAVING 子句。