傳統做法:多次查詢、應用層迴圈累加、UNION ALL 合併。
進階做法:單一查詢內使用 CASE WHEN ... THEN ... ELSE ... END 搭配聚合函數 (SUM、COUNT、AVG 等),一次掃描完成多維度統計。
CASE input_expression
WHEN when_expression THEN result_expression
[ ...n ]
[ ELSE else_result_expression ]
END
適用於「欄位值等於某固定值」的判斷。
CASE
WHEN Boolean_expression THEN result_expression
[ ...n ]
[ ELSE else_result_expression ]
END
適用於複雜條件 (範圍、多欄位組合、函數運算)。
兩者功能等價,搜尋 CASE 更通用。
表 AccessLog:UserName, LogTime, Direction (0=進入, 1=離開), Weight (可選,攜帶重量)
SELECT
SUM(CASE WHEN Direction = 0 THEN 1 ELSE 0 END) AS EnterCount,
SUM(CASE WHEN Direction = 1 THEN 1 ELSE 0 END) AS ExitCount,
COUNT(*) AS TotalRecords
FROM AccessLog
WHERE DATEDIFF(DAY, LogTime, GETDATE()) = 0; -- 今日
SELECT
SUM(CASE WHEN Direction = 0 THEN Weight ELSE 0 END) AS EnterWeight,
SUM(CASE WHEN Direction = 1 THEN Weight ELSE 0 END) AS ExitWeight
FROM AccessLog
WHERE DATEDIFF(DAY, LogTime, GETDATE()) = 0;
SELECT
UserName,
LogTime,
CASE Direction WHEN 0 THEN '進入' ELSE '離開' END AS ActionDesc
FROM AccessLog
WHERE DATEDIFF(DAY, LogTime, GETDATE()) = 0;
SELECT
Department,
-- 性別統計
SUM(CASE WHEN Gender = 'M' THEN 1 ELSE 0 END) AS MaleCount,
SUM(CASE WHEN Gender = 'F' THEN 1 ELSE 0 END) AS FemaleCount,
-- 年齡層統計
SUM(CASE WHEN Age BETWEEN 18 AND 25 THEN 1 ELSE 0 END) AS Age_18_25,
SUM(CASE WHEN Age BETWEEN 26 AND 35 THEN 1 ELSE 0 END) AS Age_26_35,
SUM(CASE WHEN Age BETWEEN 36 AND 50 THEN 1 ELSE 0 END) AS Age_36_50,
SUM(CASE WHEN Age > 50 THEN 1 ELSE 0 END) AS Age_50Plus,
-- 薪資區間統計
SUM(CASE WHEN Salary < 30000 THEN 1 ELSE 0 END) AS Salary_LT_30K,
SUM(CASE WHEN Salary BETWEEN 30000 AND 50000 THEN 1 ELSE 0 END) AS Salary_30K_50K,
SUM(CASE WHEN Salary BETWEEN 50000 AND 80000 THEN 1 ELSE 0 END) AS Salary_50K_80K,
SUM(CASE WHEN Salary > 80000 THEN 1 ELSE 0 END) AS Salary_GT_80K,
COUNT(*) AS DeptTotal
FROM Employees
GROUP BY Department
ORDER BY Department;
-- 產出:年月 | 類別A筆數 | 類別B筆數 | 類別C筆數
SELECT
FORMAT(TransDate, 'yyyy-MM') AS YearMonth,
SUM(CASE WHEN Category = 'A' THEN 1 ELSE 0 END) AS CatA_Count,
SUM(CASE WHEN Category = 'B' THEN 1 ELSE 0 END) AS CatB_Count,
SUM(CASE WHEN Category = 'C' THEN 1 ELSE 0 END) AS CatC_Count,
COUNT(*) AS Total_Count
FROM Transactions
WHERE TransDate >= DATEADD(MONTH, -12, GETDATE())
GROUP BY FORMAT(TransDate, 'yyyy-MM')
ORDER BY YearMonth;
SELECT
UserID,
LogTime,
Direction,
-- 當日迄今累計進入次數
SUM(CASE WHEN Direction = 0 THEN 1 ELSE 0 END) OVER (
PARTITION BY UserID, CAST(LogTime AS DATE)
ORDER BY LogTime ROWS UNBOUNDED PRECEDING
) AS DailyEnterSeq,
-- 當日總進入次數
SUM(CASE WHEN Direction = 0 THEN 1 ELSE 0 END) OVER (
PARTITION BY UserID, CAST(LogTime AS DATE)
) AS DailyEnterTotal
FROM AccessLog
WHERE LogTime >= DATEADD(DAY, -30, GETDATE());
SELECT
COUNT(*) AS Total,
SUM(CASE WHEN Status = 'Active' AND LastLogin > DATEADD(DAY, -30, GETDATE()) THEN 1 ELSE 0 END) AS Active_Recent,
SUM(CASE WHEN Status = 'Active' AND LastLogin <= DATEADD(DAY, -30, GETDATE()) THEN 1 ELSE 0 END) AS Active_Inactive,
SUM(CASE WHEN Status = 'Suspended' THEN 1 ELSE 0 END) AS Suspended,
SUM(CASE WHEN Status = 'Deleted' THEN 1 ELSE 0 END) AS Deleted
FROM Users;
WHERE DATEDIFF(DAY, LogTime, GETDATE()) = 0 會導致索引失效,改用 WHERE LogTime >= CAST(GETDATE() AS DATE) AND LogTime < DATEADD(DAY, 1, CAST(GETDATE() AS DATE))| 資料庫 | CASE 支援 | 備註 |
|---|---|---|
| SQL Server | 完整支援兩種形式 | |
| Oracle | 完整支援 | 亦有 DECODE 簡寫 |
| MySQL | 完整支援 | IF()、IFNULL() 簡寫 |
| PostgreSQL | 完整支援 | 標準 SQL |
| DB2 | 完整支援 | |
| ANSI SQL | 標準語法 | 搜尋 CASE 為核心 |
| 錯誤 | 正確寫法 | 說明 |
|---|---|---|
SUM(CASE WHEN col = 1 THEN col ELSE NULL END) | SUM(CASE WHEN col = 1 THEN 1 ELSE 0 END) | 聚合需數值,ELSE 0 確保型別一致;NULL 會被 SUM 忽略但型別可能推斷錯誤 |
| 忘記 ELSE 導致 NULL | 總是寫 ELSE 0 (計數) 或 ELSE NULL (需保留 NULL 語義) | 無 ELSE 時預設 ELSE NULL |
| 字串聚合用 SUM | 用 STRING_AGG (SQL 2017+)、LISTAGG (Oracle)、GROUP_CONCAT (MySQL) | SUM 僅適用數值 |
| 條件重疊/遺漏 | 用互斥條件或加上 ELSE 'Other' 兜底 | 確保每列只被歸類一次 |
AS EnterCount 而非 AS cnt1條件聚合是 SQL 開發者從「寫查詢」進階到「寫報表」的關鍵技能,善用它能大幅減少資料庫往返、簡化應用代碼、提升報表效能。