統計報表常需「按年/月/日群組」,但資料欄位為精確 DATETIME。兩大主流截取法各有優劣。
-- 取年月日組合成日期 (歸零時間)
CAST(
STR(YEAR(GETDATE())) + '/' +
STR(MONTH(GETDATE())) + '/' +
STR(DAY(GETDATE()))
AS DATETIME)
-- 僅需年月
CAST(
STR(YEAR(GETDATE())) + '/' +
STR(MONTH(GETDATE())) + '/01'
AS DATETIME)
或直接用 DATEPART / YEAR / MONTH / DAY 於 GROUP BY:
SELECT YEAR(OrderDate) AS yr, MONTH(OrderDate) AS mo, COUNT(*)
FROM Orders GROUP BY YEAR(OrderDate), MONTH(OrderDate);
-- 取得「所屬月份第一天 00:00:00」
DATEADD(MONTH, DATEDIFF(MONTH, 0, OrderDate), 0)
-- 原理:算出與基準日 1900-01-01 相差月數,再加回基準日
-- 優點:可直接套用在 WHERE 條件,利用 OrderDate 索引 Seek
WHERE OrderDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)
AND OrderDate < DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+1, 0)
| 場景 | 建議寫法 | 理由 |
|---|---|---|
| SELECT 清單 / GROUP BY | YEAR()/MONTH()/DATEPART() | 可讀性高,編譯器可優化 |
| WHERE 篩選 / JOIN 條件 | DATEADD/DATEDIFF 歸零法 | 保持 SARGable,可用索引 Range Seek |
| 計算「本月第一天」常數 | DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) | 單一運算,無函數包裹欄位 |
-- 錯誤:對欄位套用函數導致索引失效
WHERE YEAR(OrderDate) = 2023 AND MONTH(OrderDate) = 10
-- 正確:改用區間比較
WHERE OrderDate >= '2023-10-01' AND OrderDate < '2023-11-01'