別再算錯了:SQL AVG() 函數的 NULL 陷阱(三個解法讓你的數據分析不再出錯)

更新 發佈閱讀 17 分鐘
raw-image

如果你曾經盯著 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](<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 新手的五個錯誤:

  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 查詢就像個專家了。放膽去聚合吧,帶著自信!

#SQL #資料分析 #程式設計 #資料庫 #教學文章 #技術分享 #軟體開發

留言
avatar-img
留言分享你的想法!
avatar-img
Leon Wong 282
3會員
17內容數
Hi,我是 Leon Wong(亮之)——電腦科學與開發愛好者,也是 Notion 重度使用者。如果你想更高效地學習與創作,這裡會是你的實用資源。
Leon Wong 282的其他內容
2025/10/26
上週二,為了給寫作提供測試環境,我決定在 MacBook 上創建新用戶安裝 Homebrew。聽說這是 Mac 開發者的「必備工具」,安裝過程看起來很簡單——複製一行指令、貼到終端機、等幾分鐘就好。簡單吧? 三十分鐘後,我盯著 brew: command not found 錯誤訊息發呆。一小時後
Thumbnail
2025/10/26
上週二,為了給寫作提供測試環境,我決定在 MacBook 上創建新用戶安裝 Homebrew。聽說這是 Mac 開發者的「必備工具」,安裝過程看起來很簡單——複製一行指令、貼到終端機、等幾分鐘就好。簡單吧? 三十分鐘後,我盯著 brew: command not found 錯誤訊息發呆。一小時後
Thumbnail
2025/10/26
完整解析 SQL GROUP BY 的 5 大常見錯誤,包含錯誤範例、正確寫法和實戰練習。掌握 WHERE 與 HAVING 的差異、COUNT 函數的正確用法、NULL 值處理技巧,以及 SQL 執行順序的關鍵概念。適合想提升 SQL 查詢能力的初學者和進階學習者。
Thumbnail
2025/10/26
完整解析 SQL GROUP BY 的 5 大常見錯誤,包含錯誤範例、正確寫法和實戰練習。掌握 WHERE 與 HAVING 的差異、COUNT 函數的正確用法、NULL 值處理技巧,以及 SQL 執行順序的關鍵概念。適合想提升 SQL 查詢能力的初學者和進階學習者。
Thumbnail
2025/10/26
你知道 SQL 的 COUNT() 其實有三種用法嗎?COUNT(*)、COUNT(欄位) 和 COUNT(DISTINCT 欄位) 會給出完全不同的結果。本文用實際範例教你在每種情況下該用哪一種,避免資料分析出錯。適合 SQL 初學者和想打好基礎的開發者閱讀。
Thumbnail
2025/10/26
你知道 SQL 的 COUNT() 其實有三種用法嗎?COUNT(*)、COUNT(欄位) 和 COUNT(DISTINCT 欄位) 會給出完全不同的結果。本文用實際範例教你在每種情況下該用哪一種,避免資料分析出錯。適合 SQL 初學者和想打好基礎的開發者閱讀。
Thumbnail
看更多
你可能也想看
Thumbnail
商業簡報不僅僅是呈現數據,更需要深入瞭解數據分析及有效的工具運用。本文探討於Excel中使用不同函數來改善數據處理效率,包括IF、IFS、VLOOKUP、XLOOKUP及INDEX與MATCH的結合,幫助商業人士更好地從數據中提取洞見,助力業務增值,學習優化數據分析過程,讓您的商業簡報更具影響力。
Thumbnail
商業簡報不僅僅是呈現數據,更需要深入瞭解數據分析及有效的工具運用。本文探討於Excel中使用不同函數來改善數據處理效率,包括IF、IFS、VLOOKUP、XLOOKUP及INDEX與MATCH的結合,幫助商業人士更好地從數據中提取洞見,助力業務增值,學習優化數據分析過程,讓您的商業簡報更具影響力。
Thumbnail
在資料分析過程中,透過衡量變數之間的線性或非線性關係,能有效探索數據集,篩選出重要特徵,並進行預測建模。本文介紹瞭如何理解數據、使用相關矩陣找出變數關聯性,以及利用互資訊評估變數之間的依賴程度,幫助資料科學家在建模過程中選擇適當的變數,提升模型效果。
Thumbnail
在資料分析過程中,透過衡量變數之間的線性或非線性關係,能有效探索數據集,篩選出重要特徵,並進行預測建模。本文介紹瞭如何理解數據、使用相關矩陣找出變數關聯性,以及利用互資訊評估變數之間的依賴程度,幫助資料科學家在建模過程中選擇適當的變數,提升模型效果。
Thumbnail
資料前處理(Data Preprocessing)中的重要角色-缺失值處理。從檢查、刪除到填充缺失值,以及插值法和機器學習算法的應用方法。Pandas 缺失值處理基礎方法、進階填充缺失值、鐵達尼號存活預測資料集的示例和機器學習算法填補缺失值方法的介紹與使用。
Thumbnail
資料前處理(Data Preprocessing)中的重要角色-缺失值處理。從檢查、刪除到填充缺失值,以及插值法和機器學習算法的應用方法。Pandas 缺失值處理基礎方法、進階填充缺失值、鐵達尼號存活預測資料集的示例和機器學習算法填補缺失值方法的介紹與使用。
Thumbnail
作為一名擁有多年經驗的數據分析師,我深知數據分析的重要性及其對企業決策的影響。然而,數據分析並不是在任何情況下都適用。今天我想跟你聊的事情是:在數據量不足或缺乏流程優化目的時,進行數據分析的局限性。
Thumbnail
作為一名擁有多年經驗的數據分析師,我深知數據分析的重要性及其對企業決策的影響。然而,數據分析並不是在任何情況下都適用。今天我想跟你聊的事情是:在數據量不足或缺乏流程優化目的時,進行數據分析的局限性。
Thumbnail
當我們在做很多處理時,結果可能會是List包住一些數值,例如找輪廓或連通域分析時,沒有剛好的特徵可能就會有List含(空值得)形式出現。 為了避免報錯,我們就要額外先做一些處理,先做判斷是否有值在往下一個階段。 all 和 any 是 Python 中用於檢查可迭代物件(如清單、元組、集合等)
Thumbnail
當我們在做很多處理時,結果可能會是List包住一些數值,例如找輪廓或連通域分析時,沒有剛好的特徵可能就會有List含(空值得)形式出現。 為了避免報錯,我們就要額外先做一些處理,先做判斷是否有值在往下一個階段。 all 和 any 是 Python 中用於檢查可迭代物件(如清單、元組、集合等)
Thumbnail
本文探討了在使用 pandas 處理資料時應注意的幾個關鍵點,以及如何減少因資料型態問題而產生的錯誤,確保資料的原始意義得以保留。主要包括Pandas 資料處理深入解析,尋找CSV之外的數據儲存方案,以及優化資料處理策略。
Thumbnail
本文探討了在使用 pandas 處理資料時應注意的幾個關鍵點,以及如何減少因資料型態問題而產生的錯誤,確保資料的原始意義得以保留。主要包括Pandas 資料處理深入解析,尋找CSV之外的數據儲存方案,以及優化資料處理策略。
Thumbnail
題目敘述 題目會給定一個pandas DataFrame作為輸入,要求我們在原有的資料表上,將所有在欄位quantity的缺失值填補為0。 題目的原文敘述 測試範例 Example 1: Input:+-----------------+----------+-------+ | nam
Thumbnail
題目敘述 題目會給定一個pandas DataFrame作為輸入,要求我們在原有的資料表上,將所有在欄位quantity的缺失值填補為0。 題目的原文敘述 測試範例 Example 1: Input:+-----------------+----------+-------+ | nam
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News