5 個讓你看起來像 SQL 新手的 GROUP BY 錯誤(以及如何修正)

更新 發佈閱讀 17 分鐘

如果你曾經盯著 SQL 錯誤訊息,納悶為什麼你那看似完美合理的 GROUP BY 查詢就是跑不動,你絕對不孤單。我記得我第一次寫 GROUP BY 查詢時,花了整整三個小時除錯,當下真心覺得是資料庫壞了。劇透:其實是我自己的問題。

GROUP BY 是 SQL 裡最強大的資料分析功能之一,但同時也是新手最容易踩雷的地方。這些錯誤在有經驗的開發者眼中一目了然,而且會讓你的程式碼看起來很業餘。最令人沮喪的是,這些 SQL GROUP BY 錯誤其實都源自於對幾個核心概念的不理解。

讀完這篇文章,你會明白 5 個最常見的 GROUP BY 錯誤、它們為什麼會發生,以及如何精準修正。更重要的是,你會理解 GROUP BY 背後的邏輯,從此每次寫查詢都能充滿自信。


錯誤 #1:選取了沒有被分組的欄位

這是讓 SQL 新手跌倒的頭號錯誤。你寫了一個看起來很合理的查詢,結果卻得到一個莫名其妙的錯誤訊息。

錯誤長這樣:

-- 錯誤寫法

SELECT 
department,    
first_name,        -- 問題就在這裡!
  COUNT(*) AS emp_count
FROM employees
GROUP BY department;

為什麼會犯這個錯? 當你在思考資料時,這個邏輯看起來很直覺:「給我看部門、員工姓名,還有每個部門有幾個人。」你的大腦會把這三個視為你想看到的獨立資訊。

但 SQL 看到的是:你要求它把多筆資料合併成每個部門一筆。業務部可能有 10 個員工——John、Sarah、Mike 等等。它該顯示哪個 first_name?SQL 不可能隨機挑一個。

你必須記住的核心規則:

你 SELECT 語句中的每個欄位,必須符合以下兩種情況之一:

  1. 出現在 GROUP BY 子句中,或是
  2. 包在聚合函數裡面(COUNT、SUM、AVG、MIN、MAX)

正確的寫法:

-- 正確寫法 - 只選取分組或聚合的欄位

SELECT 
  department,    
COUNT(*) AS emp_count
FROM employees
GROUP BY department;

如果你真的需要個別姓名和統計數字一起出現,那你可能根本不需要 GROUP BY——你需要的是視窗函數(window function,這是另一個主題了)或是用分組子查詢做 JOIN。

什麼時候會遇到這個問題: 最常見的情況是當你想在摘要統計旁邊顯示明細資料時。記住:GROUP BY 會把資料合併。如果你要明細,就別合併。


錯誤 #2:用 WHERE 而不是 HAVING

這個錯誤超級常見,因為 WHERE 通常是你最早學會的篩選關鍵字。每次需要篩選時,直覺就是用 WHERE。

錯誤寫法:

-- 錯誤寫法

SELECT 
  department, 
COUNT(*) AS emp_count
FROM employees
WHERE COUNT(*) > 5        -- 錯了!
GROUP BY department;

為什麼會犯這個錯? WHERE 是你的首選篩選工具,所以當你想要「超過 5 個員工的部門」時,本能就是用 WHERE。

但關鍵的差異在這裡:

  • WHERE 在分組之前篩選個別資料列
  • HAVING 在聚合之後篩選群組

這樣想:你沒辦法在計數之前就用 COUNT(*) 篩選。SQL 需要先依部門分組所有員工、計算數量,然後才能篩選掉 5 人以下的部門。

正確的寫法:

-- 正確寫法 - 對聚合條件使用 HAVING

SELECT 
  department,    
COUNT(*) AS emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;

你可以在同一個查詢中同時使用兩者:

SELECT 
  department,
AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2020-01-01'  -- 先篩選個別資料列
GROUP BY department
HAVING AVG(salary) > 60000;      -- 再篩選群組

在這個例子中,WHERE 先把 2020 年以前入職的員工濾掉。然後 GROUP BY 把剩下的員工分組。接著 HAVING 再把平均薪資不到 60,000 的部門濾掉。

記憶技巧: WHERE =「哪些資料列?」| HAVING =「每個群組有多少?」


錯誤 #3:忘記 COUNT(*) 和 COUNT(column) 是不同的

老實說,我用 COUNT(*) 和 COUNT(column_name) 互換著用了好幾個月,才發現它們會回傳不同的結果。

令人困惑的時刻:

-- 你預期會有 100 個客戶

SELECT COUNT(email) AS total_customers
FROM customers;

-- 結果回傳 87... 等等,另外 13 個客戶跑去哪了?

為什麼會犯這個錯? COUNT() 感覺應該永遠都是計算全部。COUNT(*) 和 COUNT(column) 之間的細微差異看起來像是不必要的複雜——直到它咬你一口。

關鍵的差異:

  • COUNT(*) 計算所有資料列(包含某些欄位是 NULL 的資料列)
  • COUNT(column) 只計算該欄位中非 NULL 的值
  • COUNT(DISTINCT column) 計算不重複的非 NULL 值

實際範例:

-- 你的 customers 資料表裡的資料:

-- customer_id | email
-- 1           | john@email.com
-- 2           | NULL
-- 3           | jane@email.com

SELECT 
  COUNT(*) AS total_rows,                -- 回傳:3
  COUNT(email) AS emails_provided,       -- 回傳:2
  COUNT(DISTINCT email) AS unique_emails -- 回傳:2
FROM customers;

客戶 #2 沒有提供電子郵件。COUNT(*) 包含他們,因為這筆資料存在。COUNT(email) 跳過他們,因為 email 欄位是 NULL。

什麼時候這很重要:

  • 資料品質分析:「有多少客戶提供了電話號碼?」用 COUNT(phone_number)。
  • 避免重複計算:「賣出了幾種不同的產品?」用 COUNT(DISTINCT product_id)。
  • 精確度:「總客戶數」(用 COUNT(*))vs「有電子郵件的客戶數」(用 COUNT(email))。

解決方法: 刻意選擇你需要的 COUNT。問自己:「我在計算的東西,NULL 值重要嗎?」


錯誤 #4:沒有處理聚合函數中的 NULL

這個錯誤很陰險,因為你的查詢會成功執行——只是給你錯誤的答案。

看不見的錯誤:

SELECT AVG(discount) AS avg_discount
FROM orders;

-- 回傳 15.5,但你預期是 10.8
-- 發生什麼事了?

為什麼會犯這個錯? 你假設 NULL 值會被當作 0 來計算。錯了,它們會被完全忽略。

令人意外的行為:

-- 訂單資料範例:

-- order_id | discount
-- 1        | 10
-- 2        | 20
-- 3        | NULL
-- 4        | 15

SELECT AVG(discount) FROM orders;

-- 回傳:15(計算方式是 (10+20+15)/3
-- 不是:11.25(這樣算才對:(10+20+0+15)/4

NULL 值會從加總和計數中都被排除。所以如果 NULL 代表「沒有折扣」(本質上是 0),你的平均值就錯了。你計算的是「有給折扣時的平均折扣」而不是「每筆訂單的平均折扣」。

修正方法——選項 1(把 NULL 當作 0):

SELECT AVG(COALESCE(discount, 0)) AS avg_discount
FROM orders;

-- COALESCE 回傳第一個非 NULL 的值
-- 現在 NULL 在平均前會被替換成 0

修正方法——選項 2(兩種觀點都顯示):

SELECT 
  AVG(discount) AS avg_discount_when_given,
  AVG(COALESCE(discount, 0)) AS avg_discount_per_order
FROM orders;

所有聚合函數都會忽略 NULL:

  • SUM(column) - NULL 被排除在總和之外
  • MIN(column)、MAX(column) - 忽略 NULL
  • COUNT(column) - 不計算 NULL

唯一的例外是 COUNT(*),它會計算所有資料列,不管是否有 NULL 值。

什麼時候會遇到這個問題: 財務計算、選填欄位、有遺漏值的資料、轉換率計算。


錯誤 #5:在 GROUP BY 中重複複雜的表達式

這個錯誤不會造成錯誤——只是讓你的程式碼又醜又難讀,而且容易打錯字。

尷尬的查詢:

-- 能跑,但維護起來很痛苦

SELECT 
CASE 
   WHEN age < 30 THEN '30 歲以下'
    WHEN age BETWEEN 30 AND 50 THEN '30-50 歲'
    ELSE '50 歲以上'
  END AS age_group,
  COUNT(*) AS customer_count
FROM customers
GROUP BY 
CASE 
  WHEN age < 30 THEN '30 歲以下'
WHEN age BETWEEN 30 AND 50 THEN '30-50 歲'
    ELSE '50 歲以上'
  END;

為什麼會犯這個錯? 你期待欄位別名在 GROUP BY 中可以用,就像在 ORDER BY 中一樣。不幸的是,在標準 SQL 中不行。

現實是: 你必須重複整個表達式,因為 GROUP BY 在 SELECT 之前執行(別名是在 SELECT 中建立的)。SQL 還不知道 age_group 是什麼。

更好的做法——使用子查詢:

SELECT 
  age_group,
COUNT(*) AS customer_count
FROM (
  SELECT 
CASE 
     WHEN age < 30 THEN '30 歲以下'
      WHEN age BETWEEN 30 AND 50 THEN '30-50 歲'
      ELSE '50 歲以上'
    END AS age_group
  FROM customers
) AS categorized
GROUP BY age_group;

現在表達式在子查詢中只寫一次,外層查詢使用乾淨的別名。

注意: PostgreSQL 和其他某些資料庫允許 GROUP BY 1(位置)或 GROUP BY age_group(別名),但這不是標準 SQL。如果你寫的查詢可能在不同資料庫上執行,就用子查詢的方法。


加碼:為什麼會犯這些錯誤(執行順序的秘密)

大多數 GROUP BY 錯誤都源自一個誤解:SQL 不是按照你寫的順序執行查詢的。

你寫的順序:

SELECT ...
FROM ...
WHERE ...
GROUP BY ... 
HAVING ... 
ORDER BY ... 
LIMIT ...

SQL 執行的順序:

1. FROM       — 從資料表取得資料
2. WHERE      — 篩選個別資料列
3. GROUP BY   — 分組剩餘的資料列
4. HAVING     — 篩選群組
5. SELECT     — 選取欄位並計算
6. ORDER BY   — 排序結果
7. LIMIT      — 限制輸出

這個執行順序解釋了一切:

  • 為什麼 WHERE 不能用聚合函數: 在步驟 2 時聚合還不存在。
  • 為什麼你不能在 GROUP BY 中使用 SELECT 的別名: SELECT 在 GROUP BY 之後才執行。
  • 為什麼 HAVING 可以篩選聚合: 聚合是在步驟 3 的 GROUP BY 期間計算的。

一旦你內化了這個順序,GROUP BY 查詢就會變得直覺。

記憶口訣:了再好來、最後制」(From、Where、Group、Having、Select、Order、Limit)


結語

讓你看起來像 SQL 新手的五個錯誤:

  1. 選取沒有分組的欄位卻沒有聚合它們
  2. 使用 WHERE 來篩選聚合,而不是用 HAVING
  3. 混淆 COUNT(*) 和 COUNT(column)
  4. 忽略聚合函數中的 NULL 行為
  5. 重複複雜表達式而不是使用子查詢

把所有東西串起來的關鍵洞察是:大多數 GROUP BY 錯誤都來自對 SQL 執行順序的不理解。一旦你掌握了 WHERE 在分組前篩選、HAVING 在分組後篩選——而且 SELECT 是最後才執行——所有東西就會豁然開朗。

你的行動步驟: 下次寫 GROUP BY 查詢時,執行前先暫停,問自己:「我 SELECT 中的每個欄位,是不是都在我的 GROUP BY 裡,或是包在聚合函數裡?」這一個問題就能避免 80% 的 GROUP BY 錯誤。

記住:這些錯誤完全正常。每個 SQL 開發者都犯過。新手和專業人士的差別不在於避免錯誤——而在於能在 30 秒內辨識並修正,而不是花 30 分鐘。


實戰挑戰

在離開之前,試著修正這個包含多個錯誤的查詢:

-- 你能找出幾個錯誤?

SELECT 
department,
first_name,
COUNT(*) AS emp_count,
  AVG(bonus) AS avg_bonus
FROM employees
WHERE COUNT(*) > 3
GROUP BY department;

答案: 三個錯誤!

  1. first_name 沒有在 GROUP BY 中,也沒有在聚合函數裡
  2. WHERE 使用了聚合函數(應該用 HAVING)
  3. AVG(bonus) 會忽略 NULL——如果有些員工的獎金是 NULL,要決定這是不是你要的

修正後的查詢:

SELECT 
department,
COUNT(*) AS emp_count,
  AVG(COALESCE(bonus, 0)) AS avg_bonus
FROM employees
GROUP BY department
HAVING COUNT(*) > 3;

現在你寫 GROUP BY 查詢就像個專家了。放膽去聚合吧,帶著自信!



留言
avatar-img
留言分享你的想法!
avatar-img
Leon Wong 282
3會員
18內容數
Hi,我是 Leon Wong(亮之)——電腦科學與開發愛好者,也是 Notion 重度使用者。如果你想更高效地學習與創作,這裡會是你的實用資源。
Leon Wong 282的其他內容
2025/10/26
你知道 SQL 的 COUNT() 其實有三種用法嗎?COUNT(*)、COUNT(欄位) 和 COUNT(DISTINCT 欄位) 會給出完全不同的結果。本文用實際範例教你在每種情況下該用哪一種,避免資料分析出錯。適合 SQL 初學者和想打好基礎的開發者閱讀。
Thumbnail
2025/10/26
你知道 SQL 的 COUNT() 其實有三種用法嗎?COUNT(*)、COUNT(欄位) 和 COUNT(DISTINCT 欄位) 會給出完全不同的結果。本文用實際範例教你在每種情況下該用哪一種,避免資料分析出錯。適合 SQL 初學者和想打好基礎的開發者閱讀。
Thumbnail
2025/10/25
剛裝好 VS Code 卻不知道怎麼設定?這 7 個內建設定只要 5 分鐘,就能讓你避免程式碼遺失、排版混亂、眼睛疲勞等新手常見問題。不需要任何擴充套件,立即提升寫程式體驗。
Thumbnail
2025/10/25
剛裝好 VS Code 卻不知道怎麼設定?這 7 個內建設定只要 5 分鐘,就能讓你避免程式碼遺失、排版混亂、眼睛疲勞等新手常見問題。不需要任何擴充套件,立即提升寫程式體驗。
Thumbnail
2025/10/24
手動下載軟體浪費時間又有資安風險。這篇文章教你用 10 分鐘設定 Homebrew 套件管理工具,從此一行指令就能安裝、更新所有 Mac 軟體。適合完全新手,只需要會複製貼上。
Thumbnail
2025/10/24
手動下載軟體浪費時間又有資安風險。這篇文章教你用 10 分鐘設定 Homebrew 套件管理工具,從此一行指令就能安裝、更新所有 Mac 軟體。適合完全新手,只需要會複製貼上。
Thumbnail
看更多
你可能也想看
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
※ 何時該使用 JOIN? JOIN 使用的時機是:當你需要同時查詢一張以上的資料表的時候。 ※ SQL有哪些TABLE JOIN的方式? INNER JOIN LEFT JOIN RIGHT JOIN SELF JOIN ※ 使用 JOIN 的時候,我們需要考慮到: 我要使用哪一種
Thumbnail
※ 何時該使用 JOIN? JOIN 使用的時機是:當你需要同時查詢一張以上的資料表的時候。 ※ SQL有哪些TABLE JOIN的方式? INNER JOIN LEFT JOIN RIGHT JOIN SELF JOIN ※ 使用 JOIN 的時候,我們需要考慮到: 我要使用哪一種
Thumbnail
※ 關聯式資料庫(RDBMS)是什麼? 關聯式資料庫(RDBMS)是一種傳統的資料庫系統,以結構化查詢語言(SQL)為基礎,將資料儲存於預定義的表格中。這些表格包括行和列,彼此之間存在明確的關聯性。 ※ 關聯式資料庫(RDBMS)有兩個重要元素: 關聯(Relational): 關聯式資料庫
Thumbnail
※ 關聯式資料庫(RDBMS)是什麼? 關聯式資料庫(RDBMS)是一種傳統的資料庫系統,以結構化查詢語言(SQL)為基礎,將資料儲存於預定義的表格中。這些表格包括行和列,彼此之間存在明確的關聯性。 ※ 關聯式資料庫(RDBMS)有兩個重要元素: 關聯(Relational): 關聯式資料庫
Thumbnail
題目敘述 題目會給我們一張Views資料表。裡面分別有article_id、author_id、viewer_id、view_date等欄位。題目說這張資料表沒有主鍵Primary key,而且可能有重複欄位。 題目要求我們列出所有讀過自己寫的文章的作者ID 輸出答案時,請以作者ID做升序排列
Thumbnail
題目敘述 題目會給我們一張Views資料表。裡面分別有article_id、author_id、viewer_id、view_date等欄位。題目說這張資料表沒有主鍵Primary key,而且可能有重複欄位。 題目要求我們列出所有讀過自己寫的文章的作者ID 輸出答案時,請以作者ID做升序排列
Thumbnail
聚合函數 可以對資料的筆數、平均、最大、最小和加總的運算,提供查詢結果:如下表示: COUNT(Column):計算筆數,「*」是統計紀錄數。 AVG(Column):計算欄位平均值。 MAX(Column):計算欄位最大值。 MIN(Column):計算欄位最小值。 SUM(Colum
Thumbnail
聚合函數 可以對資料的筆數、平均、最大、最小和加總的運算,提供查詢結果:如下表示: COUNT(Column):計算筆數,「*」是統計紀錄數。 AVG(Column):計算欄位平均值。 MAX(Column):計算欄位最大值。 MIN(Column):計算欄位最小值。 SUM(Colum
Thumbnail
多條件查詢 AND運算子 SELECT *​ FROM your_table_name WHERE column1 LIKE '_value1%' AND column2 = number​2 OR運算子 SELECT *​ FROM your_table_name WHERE colu
Thumbnail
多條件查詢 AND運算子 SELECT *​ FROM your_table_name WHERE column1 LIKE '_value1%' AND column2 = number​2 OR運算子 SELECT *​ FROM your_table_name WHERE colu
Thumbnail
查詢範圍 指定欄位 SELECT column1, column2, column3,... FROM your_table_name 不重複欄位 SELECT DISTINCT column1 FROM your_table_name 欄位別名 SELECT column1 A
Thumbnail
查詢範圍 指定欄位 SELECT column1, column2, column3,... FROM your_table_name 不重複欄位 SELECT DISTINCT column1 FROM your_table_name 欄位別名 SELECT column1 A
Thumbnail
如果你 WHERE 的條件需要常常更動,但每次都要進去 QUERY 所在的儲存格編輯語法,實在是有點太麻煩了?但其實有個小技巧,可以讓 QUERY 更新得更輕鬆。來看看吧!
Thumbnail
如果你 WHERE 的條件需要常常更動,但每次都要進去 QUERY 所在的儲存格編輯語法,實在是有點太麻煩了?但其實有個小技巧,可以讓 QUERY 更新得更輕鬆。來看看吧!
Thumbnail
你知道 SELECT 除了回傳欄位、使用聚集函數外,還可以更進一步做加減乘除喔!這個系列是 QUERY 函式大解析的第六篇文章,如果還不知道什麼是 QUERY 的話,我還是很建議你從第一篇慢慢看、跟著我們的練習實際操作,就會更有概念囉~
Thumbnail
你知道 SELECT 除了回傳欄位、使用聚集函數外,還可以更進一步做加減乘除喔!這個系列是 QUERY 函式大解析的第六篇文章,如果還不知道什麼是 QUERY 的話,我還是很建議你從第一篇慢慢看、跟著我們的練習實際操作,就會更有概念囉~
Thumbnail
今天要介紹的是進階的 SELECT 功能,可以即時對 QUERY 的結果運算,迅速取得數值的平均、總和、最大值、最小值和數量,省去拉資料透視表(pivot table)的麻煩!
Thumbnail
今天要介紹的是進階的 SELECT 功能,可以即時對 QUERY 的結果運算,迅速取得數值的平均、總和、最大值、最小值和數量,省去拉資料透視表(pivot table)的麻煩!
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News