
如果你曾經盯著 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 語句中的每個欄位,必須符合以下兩種情況之一:
- 出現在 GROUP BY 子句中,或是
- 包在聚合函數裡面(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](<mailto:john@email.com>)
-- 2 | NULL
-- 3 | [jane@email.com](<mailto: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 新手的五個錯誤:
- 選取沒有分組的欄位卻沒有聚合它們
- 使用 WHERE 來篩選聚合,而不是用 HAVING
- 混淆 COUNT(*) 和 COUNT(column)
- 忽略聚合函數中的 NULL 行為
- 重複複雜表達式而不是使用子查詢
把所有東西串起來的關鍵洞察是:大多數 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;
答案: 三個錯誤!
first_name沒有在 GROUP BY 中,也沒有在聚合函數裡WHERE使用了聚合函數(應該用 HAVING)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 查詢就像個專家了。放膽去聚合吧,帶著自信!
#SQL #資料分析 #程式設計 #資料庫 #教學文章 #技術分享 #軟體開發















