日期範圍查詢是業務系統最高頻 SQL。寫法不同,效能差异可達 **10-100 倍**。本文對比 DATEDIFF、直接比較、BETWEEN、DATEADD 等寫法,揭示 **SARGable (Search ARGument Able)** 原則,給出最佳實踐模板。
| 寫法 | 語句 | 耗時 | 執行計畫 | 評價 |
|---|---|---|---|---|
| DATEDIFF 函數 | WHERE DATEDIFF(s, GETDATE(), thetime) > 0 | ~1200 ms | **Clustered Index Scan** (全表掃描) | ❌ 最差:函數包裹欄位導致索引失效 |
| 直接比較 (推薦) | WHERE thetime > GETDATE() | ~750 ms | **Index Seek** (叢集/非叢集索引) | ✅ 最佳:SARGable,優化器可用索引尋找 |
| BETWEEN 固定範圍 | WHERE datecol BETWEEN '2005-03-29' AND '2005-04-10' | ~750 ms | Index Seek | ✅ 優:含邊界值,注意 datetime 精度陷阱 |
| 相對時間窗 (DATEADD) | WHERE datecol >= DATEADD(day, -3, GETDATE()) | ~750 ms | Index Seek | ✅ 優:動態視窗,SARGable |
| DATEDIFF 近 N 天 | WHERE DATEDIFF(d, datecol, GETDATE()) < 3 | ~1200 ms | Clustered Index Scan | ❌ 差:同樣破壞 SARGable |
**查詢條件必須讓優化器能夠使用索引尋找**。
Column > Value, Column >= Value, Column BETWEEN Value1 AND Value2, Column IN (...), Column LIKE 'prefix%'FUNCTION(Column) > Value, Column + 1 > Value, COLUMN = UPPER('abc'), YEAR(Column) = 2024鐵律:**永遠不要在 WHERE/JOIN/ON 條件中對欄位套用函數/運算**。將運算移至常數側:
WHERE OrderDate >= DATEADD(day, -7, GETDATE())而非WHERE DATEDIFF(day, OrderDate, GETDATE()) <= 7。
-- 1. 固定日期範圍 (含邊界)
WHERE OrderDate >= '2024-01-01' AND OrderDate < '2024-02-01' -- 推薦:半開區間 [start, end)
-- 或
WHERE OrderDate BETWEEN '2024-01-01' AND '2024-01-31 23:59:59.997' -- 注意 datetime 精度 3.33ms
-- 2. 相對時間:過去 N 天/月/年
WHERE OrderDate >= DATEADD(day, -30, GETDATE()) -- 近 30 天
WHERE OrderDate >= DATEADD(month, -3, GETDATE()) -- 近 3 個月
WHERE OrderDate >= DATEADD(year, -1, GETDATE()) -- 近 1 年
-- 3. 本月 / 本週 / 今天
WHERE OrderDate >= DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) -- 本月 1 號 00:00
WHERE OrderDate >= DATEADD(week, DATEDIFF(week, 0, GETDATE()), 0) -- 本週一 00:00
WHERE OrderDate >= CAST(GETDATE() AS DATE) -- 今天 00:00 (SQL 2008+)
-- 4. 年初至今 (YTD)
WHERE OrderDate >= DATEADD(year, DATEDIFF(year, 0, GETDATE()), 0)
datetime 精度 **3.33 毫秒**,最大值 23:59:59.997。BETWEEN '2024-01-01' AND '2024-01-31 23:59:59.999' 會**漏掉 23:59:59.998/.999**。WHERE dt >= '2024-01-01' AND dt < '2024-02-01',或改用 datetime2(3) 精度 1ms。WHERE CAST(OrderDate AS DATE) = '2024-01-15' → 破壞 SARGable。WHERE OrderDate >= '2024-01-15' AND OrderDate < '2024-01-16'。CREATE PROC usp_GetOrdersByDateRange
@StartDate DATETIME2 = NULL,
@EndDate DATETIME2 = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT OrderId, CustomerId, OrderDate, TotalAmount
FROM Orders
WHERE (@StartDate IS NULL OR OrderDate >= @StartDate)
AND (@EndDate IS NULL OR OrderDate < DATEADD(day, 1, @EndDate)) -- 含結束日
ORDER BY OrderDate DESC;
END;
遵循 SARGable 原則,日期查詢效能穩定可預測,是 SQL 開發與調優的基本功。