本文 AI 產出,尚未審核

MySQL – 聚合函式與分組

主題:HAVING 過濾分組資料


簡介

在資料庫查詢中,我們常常需要先 將資料依某個欄位分組,再針對每個組別計算總和、平均值、最大值等聚合結果。
GROUP BY 能夠完成「分組」的工作,但如果只想保留 符合特定條件的組別,單靠 WHERE 已經不夠,因為 WHERE 是在分組 之前 進行過濾。

此時就需要 HAVING 子句。HAVING 允許我們在 聚合計算完成後 再篩選組別,讓查詢結果更精準且效能更好。對於報表、統計分析、商業智慧等應用,掌握 HAVING 的使用是必備技能。


核心概念

1. HAVINGWHERE 的差別

條件 執行階段 作用對象
WHERE 分組前 原始列 (row)
HAVING 分組後 聚合結果 (如 SUM(), COUNT() …)

簡單說WHERE 篩選單筆資料,HAVING 篩選「一群資料」的統計結果。


2. 基本語法

SELECT <欄位列表>, <聚合函式>(<欄位>) AS <別名>
FROM <資料表>
WHERE <前置條件>          -- (可選) 先過濾原始列
GROUP BY <分組欄位>
HAVING <聚合條件>        -- 必須使用聚合函式或別名
ORDER BY <排序欄位>;
  • HAVING 可以使用聚合函式,也可以直接使用在 SELECT 中定義的別名。
  • 多個條件可使用 ANDOR 串接,寫法與 WHERE 完全相同。

3. 常見的聚合函式

函式 說明
COUNT(*) 計算組內列數
SUM(col) 計算組內數值總和
AVG(col) 計算組內平均值
MAX(col) / MIN(col) 取得最大 / 最小值

程式碼範例

以下示範使用 MySQL 8.0,資料表 orders 結構簡化如下:

CREATE TABLE orders (
    order_id   INT PRIMARY KEY,
    customer_id INT,
    order_date DATE,
    amount     DECIMAL(10,2)
);

範例 1:找出 每位客戶 的總消費金額,且只保留總金額 大於 5000 的客戶

SELECT 
    customer_id,
    SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id
HAVING total_spent > 5000;

說明SUM(amount) 先算出每個 customer_id 的消費總額,HAVING 再把小於 5000 的組別剔除。


範例 2:統計 每個月份 的訂單筆數,僅顯示 筆數不少於 10 的月份

SELECT 
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    COUNT(*) AS order_cnt
FROM orders
GROUP BY month
HAVING order_cnt >= 10
ORDER BY month;

技巧:使用 DATE_FORMAT 產生月份字串作為分組依據,HAVING 直接使用 order_cnt 別名。


範例 3:找出 平均訂單金額 超過 1000 的客戶,同時篩除訂單筆數少於 5 的客戶

SELECT 
    customer_id,
    AVG(amount)   AS avg_amount,
    COUNT(*)      AS order_cnt
FROM orders
GROUP BY customer_id
HAVING avg_amount > 1000 AND order_cnt >= 5;

重點HAVING 可同時檢查多個聚合條件,條件之間使用 ANDOR


範例 4:使用子查詢結合 HAVING,取得 每個客戶 最近一年內的 最高單筆金額,且該金額 超過 2000

SELECT 
    customer_id,
    MAX(amount) AS max_amount
FROM orders
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
GROUP BY customer_id
HAVING max_amount > 2000;

說明:先以 WHERE 限制時間範圍,再以 HAVING 檢查聚合結果。


範例 5:結合 ROLLUP 產生小計與總計,並只保留 金額總和 大於 10000 的層級

SELECT 
    IFNULL(customer_id, 'TOTAL') AS cust,
    SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id WITH ROLLUP
HAVING total_amount > 10000;

提示ROLLUP 會產生額外的「小計」與「總計」列,HAVING 同樣適用於這些衍生列。


常見陷阱與最佳實踐

陷阱 說明 建議的解決方式
忘記加 GROUP BY HAVING 必須與 GROUP BY 搭配使用,否則會產生錯誤或不符合預期。 確認查詢邏輯真的需要分組,再加入 GROUP BY
HAVING 使用非聚合欄位 雖然 MySQL 允許,但會產生不確定結果,因為非聚合欄位在分組後可能有多個值。 僅在 HAVING 使用聚合函式或 SELECT 別名。
WHEREHAVING 重複條件 把可以在 WHERE 做的過濾寫在 HAVING,會造成不必要的聚合運算,降低效能。 把可在資料列層面過濾的條件放到 WHERE,只在需要聚合結果時才用 HAVING
忘記別名或函式重複 HAVING 中直接寫 SUM(col) > 0,但 SELECT 也用了相同的聚合,易造成閱讀困難。 為聚合結果取有意義的別名,然後在 HAVING 使用別名,提升可讀性。
使用 HAVING 產生全表掃描 若條件過於寬鬆,會把所有分組都算出來再過濾,效能不佳。 盡量在 WHERE 先縮小資料範圍,或使用適當的索引。

最佳實踐小結

  1. 先用 WHERE 篩選,再用 GROUP BY 分組,最後用 HAVING 濾組。
  2. 為聚合結果取別名,在 HAVING 中直接使用別名,讓 SQL 更易讀。
  3. 檢查執行計畫EXPLAIN),確保 WHERE 條件被索引利用,避免不必要的全表掃描。
  4. 避免在 HAVING 中使用非聚合欄位,除非確定該欄位在每個組內值唯一。

實際應用場景

場景 需求 HAVING 的角色
銷售報表 列出每月營收超過目標的月份 GROUP BY 月份,HAVING 只保留營收 > 目標
客戶分層 找出購買次數達 10 次且平均金額 > 2000 的 VIP 客戶 GROUP BY customer_idHAVING 同時檢查 COUNTAVG
庫存警示 列出庫存量低於安全庫存且最近 30 天出貨量超過 100 的商品 WHERE 限制日期,GROUP BY product_idHAVING 同時檢查 SUM(quantity)stock
網站流量分析 只關注每日訪問次數超過 5000 的日期 GROUP BY DATE(access_time)HAVING 篩選 COUNT(*) > 5000
金融風控 找出信用卡持卡人中,該月消費金額超過 5 萬且交易次數超過 20 筆的高風險客戶 GROUP BY card_no, monthHAVING 同時檢查 SUM(amount)COUNT(*)

總結

  • HAVING在聚合後過濾組別 的關鍵子句,與 WHERE 互補。
  • 正確的查詢順序是:WHEREGROUP BYHAVINGSELECTORDER BY
  • 透過別名、適當的索引與前置過濾,可讓 HAVING 的使用既 高效易讀
  • 在實務上,HAVING 常被用於 報表、分層、警示 等需要根據統計結果進一步篩選的情境。

掌握了 HAVING,你就能在 MySQL 中寫出更具彈性、效能更佳的資料分析查詢,為業務決策提供可靠的數據支撐。祝你在 MySQL 的世界裡玩得開心,查詢寫得順手!