一句話核心差異
WHERE 在分組前過濾行 (不可用聚合函數);HAVING 在分組後過濾組 (可用聚合函數)。
執行順序視覺化
FROM t1 JOIN t2 ...
↓
WHERE 條件過濾行 (WHERE 可用索引)
↓
GROUP BY 分組
↓
聚合函數計算 (COUNT, SUM, AVG...)
↓
HAVING 條件過濾組 (可用聚合函數)
↓
SELECT 投影
↓
ORDER BY 排序
↓
LIMIT 限制
對比範例:Northwind 資料庫
場景一:WHERE 過濾明細行
-- 找訂單金額 > 1000 的訂單明細
SELECT OrderID, SUM(UnitPrice * Quantity) AS Total
FROM OrderDetails
WHERE UnitPrice > 50 -- 明細行級過濾
GROUP BY OrderID;
場景二:HAVING 過濾分組結果
-- 找總金額 > 10000 的訂單
SELECT OrderID, SUM(UnitPrice * Quantity) AS Total
FROM OrderDetails
GROUP BY OrderID
HAVING SUM(UnitPrice * Quantity) > 10000; -- 組級過濾,需聚合函數
場景三:兩者結合
-- 1997 年、總金額 > 5000 的訂單
SELECT o.OrderID, SUM(od.UnitPrice * od.Quantity) AS Total
FROM Orders o
JOIN OrderDetails od ON o.OrderID = od.OrderID
WHERE o.OrderDate >= '1997-01-01' AND o.OrderDate < '1998-01-01' -- 行過濾
GROUP BY o.OrderID
HAVING SUM(od.UnitPrice * od.Quantity) > 5000; -- 組過濾
常見錯誤與修正
| 錯誤寫法 | 錯誤原因 | 正確寫法 |
| HAVING price > 100 | price 非聚合欄位,非 GROUP BY 列 | WHERE price > 100 (行過濾) 或 HAVING MAX(price) > 100 (組過濾) |
| WHERE COUNT(*) > 5 | WHERE 階段尚未聚合 | HAVING COUNT(*) > 5 |
| WHERE col NOT IN (subquery 含 NULL) | NOT IN 遇 NULL 返回 UNKNOWN | WHERE NOT EXISTS (subquery) 或 LEFT JOIN ... IS NULL |
效能啟示
- WHERE 能用索引:盡早過濾,減少參與分組/排序的行數
- HAVING 無索引:在結果集上掃描,儘量將條件下推到 WHERE
- 索引覆蓋:WHERE + GROUP BY 欄位建立複合索引可避免回表
記憶口訣
WHERE 管「行」,行行有索引;
HAVING 管「組」,組組看聚合。
先 WHERE 後 HAVING,順序不能亂。