現象:同一張表,WHERE item_local_code='03004' AND DATEDIFF(day, sub_para, GETDATE())=29 偶發「轉換 datetime 失敗」,但 sub_para 為 varchar(8) 儲存日期字串,部分列含髒資料 (如 '2003a1-1')。錯誤發生與否取決於**執行計畫中條件評估順序**,而順序受索引影響。
CREATE TABLE tb (
item_local_code CHAR(5),
sub_del_flag INT,
sub_para VARCHAR(10),
CONSTRAINT PK_t PRIMARY KEY (sub_para, item_local_code) -- 叢集索引鍵順序
);
INSERT tb SELECT '03004', 1, '2003-1-1'
UNION ALL SELECT '03005', 1, '2003a1-1'; -- 髒資料
-- 查詢:優化器可能先用 PK 索引求 sub_para,導致髒資料參與 DATEDIFF 轉型報錯
SELECT * FROM tb
WHERE item_local_code='03004'
AND sub_del_flag<>0
AND DATEDIFF(day, sub_para, GETDATE()) > 29;
-- Msg 241: 轉換字串為 datetime 時發生語法錯誤。
(sub_para, item_local_code) 使優化器傾向 **Index Seek → Key Lookup** 先篩選 sub_para。DATEDIFF(day, sub_para, ...) 運算式在 **Filter 運算子** 中評估,**早於** item_local_code 篩選。'2003a1-1' 被讀取並嘗試轉型 → 拋錯,即使最終不符合 item_local_code='03004' 也會報錯。SELECT * FROM (
SELECT * FROM tb
WHERE item_local_code='03004'
AND sub_del_flag<>0
AND ISDATE(sub_para)=1 -- 先過濾合法日期格式
) AS CleanData
WHERE DATEDIFF(day, sub_para, GETDATE()) > 29;
內層查詢先套用 ISDATE=1 過濾髒資料,外層才執行日期運算,邏輯順序固定不受索引影響。
SELECT * FROM tb
WHERE item_local_code='03004'
AND sub_del_flag<>0
AND TRY_CONVERT(DATE, sub_para) IS NOT NULL
AND DATEDIFF(day, TRY_CONVERT(DATE, sub_para), GETDATE()) > 29;
TRY_CONVERT 失敗回傳 NULL 不拋錯,配合 IS NOT NULL 完成安全過濾。
CREATE INDEX IX_tb_SafeQuery ON tb (item_local_code, sub_del_flag) INCLUDE (sub_para);
優化器改用此非叢集索引直接求 item_local_code,大幅降低讀取髒資料機率 (但非 100% 保證,仍建議配合方法一)。
DATE/DATETIME 而非字串。ISDATE=1 或 TRY_CONVERT 守衛。