WHERE 與 HAVING 同為過濾子句,但作用階段、作用對象、可用函數截然不同。搞清二者邊界是編寫高效聚合查詢的關鍵。
| 維度 | WHERE | HAVING |
|---|---|---|
| 執行順序 | 第 2 步 (FROM/JOIN 後,GROUP BY 前) | 第 4 步 (GROUP BY 聚合後,SELECT 前) |
| 作用對象 | 單行 (輸入行) | 分組 (聚合後的組) |
| 可用聚合函數 | 不可用 | 可用 (SUM, COUNT, AVG, MIN, MAX 等) |
| 可用列 | 表列、衍生列、別名(視資料庫) | 分組列、聚合表達式 |
| 效能特徵 | 早期過濾,減少參與聚合的行數 | 晚期過濾,聚合完成後再篩選組 |
1. FROM / JOIN -- 確定數據源
2. WHERE -- 行級過濾
3. GROUP BY -- 分組
4. HAVING -- 組級過濾 (可用聚合)
5. SELECT -- 投影列、別名
6. ORDER BY -- 排序
7. OFFSET / FETCH -- 分頁
-- 表結構: name, region, area, population, gdp
SELECT Region, SUM(Population) AS TotalPop, SUM(Area) AS TotalArea
FROM BBC
GROUP BY Region;
SELECT Region, SUM(Population), SUM(Area)
FROM BBC
GROUP BY Region
HAVING SUM(Area) > 1000000; -- 必須用 HAVING,因無單行 Area > 1000000
錯誤寫法:WHERE SUM(Area) > 1000000 → 報錯「聚合函數不允許在 WHERE 中」。
SELECT Region, SUM(Population) AS Pop, SUM(Area) AS Area
FROM BBC
GROUP BY Region
HAVING SUM(Population) > 100000000 AND SUM(Area) > 1000000;
-- 步驟 1: 分組計數
SELECT Email, COUNT(*) AS Cnt FROM Edm_User GROUP BY Email;
-- 步驟 2: 過濾重複 (Cnt > 1)
SELECT Email, COUNT(*) AS Cnt
FROM Edm_User
GROUP BY Email
HAVING COUNT(*) > 1; -- 等價 HAVING Cnt > 1 (MySQL 支援別名)
能在 WHERE 解決的,絕不拖到 HAVING。
-- 低效:先分組聚合全表,再過濾
SELECT Region, COUNT(*) FROM BBC GROUP BY Region HAVING Region = 'Asia';
-- 高效:先過濾行,再分組聚合
SELECT Region, COUNT(*) FROM BBC WHERE Region = 'Asia' GROUP BY Region;
前者掃描全表、聚合所有區域、最後丟棄非 Asia 組;後者僅掃描 Asia 區域行,I/O、CPU、內存均大幅降低。
SELECT Region, COUNT(*) AS Cnt
FROM BBC
GROUP BY Region
HAVING Region LIKE 'A%'; -- 僅用分組列過濾,語法合法但建議移到 WHERE
WHERE → GROUP BY → HAVING。