當使用時間相關的sql撈取資料時,由於各種因素上的影響,不論是程式面或是資料庫層面的,都會有機會影響到我們取資料時的正確性,這次剛好遇到執行的 SQL 下了時間相關的條件,但是抓出的資料預期的結果卻與實際不相符,故撰寫這篇文章來探討差異點以及解決方法。
範例探討
我們假設一個情況,要抓取2023年10月31號當天所產生的資料,常見的寫法如下,我們假設資料所在的table叫做mydata,紀錄該筆資料日期的欄位叫做custom_date:
- SELECT * FROM mydata WHERE custom_date >= 2023-10-31 00:00:00 and custom_date <= 2023-10-31 23:59:59.999
- SELECT * FROM mydata WHERE custom_date between 2023-10-31 and 2023-10-31 23:59:59.999
- SELECT * FROM mydata WHERE custom_date between 2023-10-31 and 2023-11-01
是不是覺得這幾種應該是同樣的結果?正確答案是,是也不是。
如果我們的資料紀錄的單位沒有到很精確,加上沒有在臨界值的資料,其實從結果來看並看不出什麼差別,那甚麼情況下會發生資料錯誤呢?
如果有資料是類似2023-10-31 23:59:59.980或是2023-11-01 00:00:00.000,
我們的寫法只要有一點點細微的差異就可能導致多一筆或少一筆資料。
先講結論,會影響結果的細節有:
- 正常我們在指令中宣告的時間在SQL中的預設型別為DateTime(精度為3.33毫秒),所以假設我們下了上面的第一個寫法,999的部分是會自動進位的,跟我們第三種寫法其實對資料庫來說是同一個意思,會導致2023-11-01 00.00.00:000的資料被包含進來。
- 第二個寫法其實也等價於第一個寫法,後面的部分也會自動進位導致精度丟失那假設我們將1跟2的結束時間從 2023-10-31 23:59:59.999改成 2023-10-31 23:59:59呢?
這又會導致時間是2023-10-31 23:59:59:990此類時間的資料丟失。
推薦解法
當然如果不要求時間精度,從資料寫入時下手讓精度的部分自動進位或捨去可以省略很多麻煩,但假如必須從撈資料的情況下手的話,我們必須在程式面宣告時間部分的參數為DateTime2型別(精確度為100奈秒),我們sql內紀錄的時間精度只到0.01毫秒,所以完全可以覆蓋到我們的撈取條件。
假設st跟ed是我們傳入撈取的C# DateTime型別的開始時間與結束時間,套用以上例子為2023-10-31 00:00:00及2023-10-31 23:59:59.999,我們先宣告兩個SqlParameter型別為SQL的DateTime2後,在將時間值賦予給st2跟ed2,避免精度丟失。
var st2 = new SqlParameter(“@ST”, SqlDbType.DateTime2);
var ed2 = new SqlParameter(“@ET”, SqlDbType.DateTime2);
st2.Value = st;
ed2.Value = ed;
宣告完成之後再進行我們正常流程的撈取資料:
var results = SqlHelper.GetDbData<DataModel>($@”
select *
from mydata
where sm.SendAt between @ST and @ET”,
st2,
ed2);
即可獲取正確的結果。
結論
在時間相關的 SQL 資料擷取中,即使看似相似及邏輯正確的日期條件,細微的差異也可能導致資料正確性的問題。主要的差異點在於 SQL 中時間型別的精度,以及如何處理結束時間的問題。正確處理這些細節是確保擷取資料的正確性的關鍵。
在本文中,我們探討了不同的日期條件寫法,包括使用精確到毫秒的時間和不同的比較運算符。我們發現,細微的精度差異可能會導致錯誤的資料擷取,特別是在邊界情況下。
為了解決這個問題,我們建議在程式面宣告時間參數為DateTime2型別,這樣可以確保精確度足夠高,以應對任何可能的情況。在撈取資料時,使用SqlParameter來宣告日期參數,並避免精度丟失,確保正確的資料擷取。
總之,要確保時間相關SQL資料擷取的正確性,需要仔細處理日期條件,特別是在精度要求高的情況下,以避免資料錯誤。這樣可以確保獲得預期的結果。