ทำไม datetime การค้นหาของฉันจึงไม่ตรงกัน


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

แต่ผลลัพธ์มีบันทึกที่โพสต์วันนี้: 2015-07-28 เซิร์ฟเวอร์ฐานข้อมูลของฉันไม่ได้อยู่ในประเทศของฉัน อะไรคือปัญหา ?

คำตอบ:


16

เนื่องจากคุณใช้datetimeประเภทข้อมูลคุณต้องเข้าใจว่าเซิร์ฟเวอร์ sql ปัดเศษข้อมูลวันที่และเวลาอย่างไร

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

ป้อนคำอธิบายรูปภาพที่นี่

การใช้แบบสอบถามด้านล่างคุณสามารถเห็นปัญหาการปัดเศษที่เซิร์ฟเวอร์ sql ทำเมื่อคุณใช้DATETIMEชนิดข้อมูล

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

ป้อนคำอธิบายรูปภาพที่นี่ คลิกเพื่อดูภาพขยาย

DATETIME2ได้รับรอบนับตั้งแต่ SQL Server 2008 DATETIMEเพื่อเริ่มต้นใช้มันแทน สำหรับสถานการณ์ของคุณคุณสามารถใช้datetime2กับความแม่นยำของทศนิยม 3 ตำแหน่งdatetime2(3)เช่น

ประโยชน์ของการใช้datetime2:

  • รองรับถึง 7 ตำแหน่งทศนิยมสำหรับองค์ประกอบเวลา VS datetimeสนับสนุนเพียง 3 ตำแหน่งทศนิยม .. และด้วยเหตุนี้คุณเห็นปัญหาการปัดเศษตั้งแต่เริ่มต้นโดยdatetimeรอบที่ใกล้ที่สุด.003 secondsกับการเพิ่มขึ้นของ.000, .003หรือ.007วินาที
  • datetime2เป็นมากขึ้นได้อย่างแม่นยำกว่าdatetimeและdatetime2ช่วยให้คุณควบคุมDATEและเมื่อเทียบกับTIMEdatetime

เอกสารอ้างอิง:


1
gives you control of DATE and TIME as opposed to datetime.นั่นหมายความว่าอย่างไร?
นูเรตต์

เรื่อง ใช้DateTime2กับDateTime: สำหรับ - ส่วนใหญ่ - ของ- โลกแห่งความจริง - การใช้งาน - คดี, ประโยชน์ของDateTime2ต้นทุน <มาก ดู: stackoverflow.com/questions/1334143/… b นั่นไม่ใช่ปัญหารูทที่นี่ ดูความคิดเห็นต่อไป
Tom

ปัญหารากที่นี่ (ตามที่ฉันเดิมพัน devs อาวุโสส่วนใหญ่จะเห็นด้วย) ไม่ใช่ความแม่นยำไม่เพียงพอในวันที่สิ้นสุดโดยรวมของการเปรียบเทียบช่วงวันที่และเวลา แต่เป็นการใช้ช่วงเวลาแบบรวม (กับพิเศษ) หนึ่งช่วงเวลา มันเหมือนกับการตรวจสอบความเท่าเทียมกันกับ Pi มีความเป็นไปได้เสมอที่หนึ่งใน #s มี> หรือ <ความแม่นยำ (เช่นเกิดอะไรขึ้นถ้าdatetime3เพิ่มความแม่นยำ 70 หลัก (เทียบกับ 7)) แนวปฏิบัติที่เหมาะสมที่สุดคือการใช้ค่าที่ความแม่นยำไม่สำคัญนั่นคือ <การเริ่มต้นของวินาทีถัดไปนาทีชั่วโมงหรือวันเทียบกับ <= จุดสิ้นสุดของวินาทีวินาทีนาทีชั่วโมงหรือวันก่อนหน้า
Tom

18

ตามที่คนอื่นหลายคนได้กล่าวถึงในความคิดเห็นและคำตอบอื่น ๆ สำหรับคำถามของคุณปัญหาหลัก2015-07-27 23:59:59.999คือการถูกปัดเศษ2015-07-28 00:00:00.000โดย SQL Server ตามเอกสารสำหรับDATETIME:

ช่วงเวลา - 00:00:00 ถึง 23: 59: 59.997

โปรดทราบว่าช่วงเวลาไม่สามารถเป็น.999ได้ นอกจากนี้ในเอกสารประกอบจะระบุกฎการปัดเศษที่ SQL Server ใช้สำหรับเลขนัยสำคัญน้อยที่สุด

ตารางแสดงกฎการปัดเศษ

โปรดสังเกตว่าตัวเลขที่มีนัยสำคัญน้อยที่สุดสามารถมีค่าที่เป็นไปได้หนึ่งในสามค่า: "0", "3" หรือ "7"

มีวิธีแก้ไขปัญหา / วิธีแก้ไขปัญหาหลายประการสำหรับสิ่งนี้ที่คุณสามารถใช้ได้

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

จากห้าตัวเลือกที่ฉันนำเสนอข้างต้นฉันจะพิจารณาตัวเลือกที่ 1 และ 3 ตัวเลือกที่ทำงานได้เท่านั้น สิ่งเหล่านี้สื่อความตั้งใจของคุณอย่างชัดเจนและจะไม่แตกถ้าคุณอัปเดตประเภทข้อมูล หากคุณใช้ SQL Server 2008 หรือใหม่กว่าฉันคิดว่าตัวเลือก 3 ควรเป็นแนวทางที่คุณต้องการ โดยเฉพาะอย่างยิ่งถ้าคุณสามารถเปลี่ยนจากการใช้DATETIMEประเภทข้อมูลเป็นDATEชนิดข้อมูลสำหรับposted_dateคอลัมน์ของคุณ

เกี่ยวกับตัวเลือกที่ 3 คำอธิบายที่ดีมากเกี่ยวกับบางประเด็นสามารถพบได้ที่นี่: การเผยแพร่ในปัจจุบันเป็นเรื่องที่น่าเบื่อ แต่เป็นความคิดที่ดีหรือไม่?

ฉันไม่ชอบตัวเลือกที่ 2 และ 5 เพราะ.997เศษเสี้ยววินาทีจะเป็นเพียงจำนวนเวทมนตร์ที่ผู้คนอยากจะ "แก้ไข" ด้วยเหตุผลเพิ่มเติมบางประการที่BETWEENไม่ได้รับการยอมรับอย่างกว้างขวางคุณอาจต้องการเช็คเอาต์โพสต์นี้

ฉันไม่ชอบตัวเลือก 4 เนื่องจากการแปลงชนิดข้อมูลเป็นสตริงเพื่อการเปรียบเทียบรู้สึกสกปรกสำหรับฉัน เหตุผลเชิงคุณภาพเพิ่มเติมเพื่อหลีกเลี่ยงใน SQL Server คือมันส่งผลกระทบต่อsargability aka คุณไม่สามารถทำการค้นหาดัชนีและที่มักจะส่งผลให้ประสิทธิภาพการทำงานต่ำ

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการที่เหมาะสมและวิธีที่ผิดในคำสั่งช่วงวันที่จับเช็คเอาต์โพสต์นี้โดยแอรอนเบอร์ทรานด์

ในการแยกทางที่คุณจะสามารถที่จะเก็บแบบสอบถามเดิมของคุณและมันจะทำงานตามที่ต้องการถ้าคุณเปลี่ยนของคุณposted_dateคอลัมน์จากไปDATETIME DATETIME2(3)ซึ่งจะช่วยประหยัดพื้นที่เก็บข้อมูลบนเซิร์ฟเวอร์ให้ความแม่นยำที่มากขึ้นด้วยความแม่นยำเดียวกันเป็นมาตรฐานที่มากขึ้น / พกพาได้และช่วยให้คุณปรับความแม่นยำ / ความแม่นยำได้อย่างง่ายดายหากความต้องการของคุณเปลี่ยนแปลงในอนาคต อย่างไรก็ตามนี่เป็นเพียงตัวเลือกหากคุณใช้ SQL Server 2008 หรือใหม่กว่า

เล็กน้อย1/300ของความแม่นยำที่สองด้วยDATETIMEดูเหมือนว่าจะถูกระงับจาก UNIX ต่อคำตอบ StackOverflowนี้ Sybase ซึ่งมีการแบ่งมรดกมีความคล้ายคลึงกัน1/300ของความถูกต้องที่สองในประเภทDATETIMEและTIMEข้อมูล แต่ตัวเลขที่สำคัญน้อยที่สุดของพวกเขาคือการสัมผัสที่แตกต่างกันที่ "0", "3" และ "6" ในความเห็นของฉันความ1/300ถูกต้องของวินาทีและ / หรือ 3.33ms เป็นการตัดสินใจทางสถาปัตยกรรมที่โชคร้ายเนื่องจากบล็อก 4 ไบต์สำหรับเวลาในDATETIMEชนิดข้อมูลของ SQL Server อาจสนับสนุนความแม่นยำ 1ms ได้อย่างง่ายดาย


ใช่ แต่หลัก "ปัญหาหลัก" ไม่ได้ใช้ตัวเลือก 1 (เช่นใช้ค่าสิ้นสุดช่วงรวม (เทียบกับพิเศษ) ใด ๆที่ความแม่นยำของประเภทข้อมูลในอดีตหรือในอนาคตอาจส่งผลต่อผลลัพธ์) มันเหมือนกับการตรวจสอบความเท่าเทียมกันกับ Pi มันเป็นไปได้เสมอหนึ่งที่ # มี> หรือ <ความแม่นยำ (ยกเว้นว่าทั้งคู่จะถูกปัดเศษล่วงหน้าเป็นความแม่นยำต่ำสุดทั่วไป) จะเกิดอะไรขึ้นถ้าdatetime3เพิ่มความแม่นยำ 70 หลัก (กับ 7) แนวปฏิบัติที่เหมาะสมที่สุดคือการใช้ค่าที่ความแม่นยำไม่สำคัญนั่นคือ <การเริ่มต้นของวินาทีถัดไปนาทีชั่วโมงหรือวันเทียบกับ <= จุดสิ้นสุดของวินาทีวินาทีนาทีชั่วโมงหรือวันก่อนหน้า
Tom

9

การแปลงโดยนัย

ฉันว่าประเภทข้อมูลที่โพสต์แล้วคือ Datetime อย่างไรก็ตามมันไม่สำคัญว่าประเภทในด้านอื่น ๆ คือ Datetime, Datetime2 หรือ Just Time เพราะสตริง (Varchar) จะถูกแปลงเป็น Datetime โดยปริยาย

ด้วย posts_date ที่ประกาศเป็น Datetime2 (หรือเวลา) ส่วนposted_date <= '2015-07-27 23:59:59.99999'คำสั่งล้มเหลวเนื่องจาก altough 23:59:59.99999เป็นค่า Datetime2 ที่ถูกต้องนี่ไม่ใช่ค่า Datetime ที่ถูกต้อง:

 Conversion failed when converting date and/or time from character string.

ช่วงเวลาสำหรับวันที่และเวลา

ช่วงเวลาของวันที่และเวลาคือ 00:00:00 ถึง 23: 59: 59.997 ดังนั้น 23: 59: 59.999 จึงอยู่นอกระยะและต้องปัดขึ้นหรือลงเพื่อให้ได้ค่าที่ใกล้เคียงที่สุด

ความถูกต้อง

นอกเหนือจากค่าของวันที่และเวลาจะถูกปัดเศษด้วยการเพิ่มขึ้นของ. 000, .003 หรือ .007 วินาที (เช่น. 000, 003, 007, 010, 013, 017, 020, ... , 997)

นี้ไม่ได้เป็นกรณีที่มีค่า2015-07-27 23:59:59.999ที่อยู่ในช่วงนี้และ2015-07-27 23:59:59.9972015-07-28 0:00:00.000

ช่วงนี้สอดคล้องกับตัวเลือกก่อนหน้าและตัวเลือกที่ใกล้เคียงที่สุดซึ่งทั้งคู่ลงท้ายด้วย .000, .003 หรือ. 007

ปัดเศษขึ้นหรือลง ?

เพราะมันอยู่ใกล้กับ2015-07-28 0:00:00.000(1 เมื่อเทียบกับ -2) มากกว่าสตริงจะถูกปัดเศษขึ้นและกลายเป็นนี้ค่าวันที่และเวลา:2015-07-27 23:59:59.9972015-07-28 0:00:00.000

ด้วยขีด จำกัด บนเช่น2015-07-27 23:59:59.998(หรือ .995, .996, .997, .998) มันจะถูกปัดเศษลง2015-07-27 23:59:59.997และแบบสอบถามของคุณจะทำงานตามที่คาดไว้ อย่างไรก็ตามมันจะไม่ได้เป็นทางออก แต่เป็นเพียงโชคดี

Datetime2 หรือประเภทเวลา

ช่วงเวลา Datetime2 และเวลา00:00:00.0000000ผ่านไป23:59:59.9999999ด้วยความแม่นยำ 100ns (ตัวเลขสุดท้ายเมื่อใช้กับความแม่นยำ 7 หลัก)

อย่างไรก็ตามช่วง Datetime (3) ไม่เหมือนกับช่วง Datetime:

  • วันที่0:0:00.000ถึง23:59:59.997
  • Datetime2 0:0:00.000000000ถึง23:59:59.999

วิธีการแก้

ในท้ายที่สุดการค้นหาวันที่ด้านล่างในวันถัดไปนั้นจะปลอดภัยกว่าหรือเท่ากับสิ่งที่คุณคิดว่าเป็นช่วงเวลาสุดท้ายของวัน นี่เป็นส่วนใหญ่เพราะคุณรู้ว่าในวันถัดไปจะเริ่มต้นที่ 0: 00: 00.000 แต่ประเภทข้อมูลที่แตกต่างกันอาจไม่มีเวลาเดียวกันในตอนท้ายของวัน:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000จะให้ผลลัพธ์ที่ถูกต้องแก่คุณและเป็นตัวเลือกที่ดีที่สุด
  • <= 2015-07-27 23:59:59.xxx อาจส่งคืนค่าที่ไม่คาดคิดเมื่อมันไม่ถูกปัดเศษขึ้นกับสิ่งที่คุณคิดว่าควรจะเป็น
  • ควรหลีกเลี่ยงการแปลงเป็นวันที่และการใช้งานฟังก์ชั่นเพราะมัน จำกัด การใช้ดัชนี

เราคิดว่าการเปลี่ยน [posted_date] เป็น Datetime2 และความแม่นยำที่สูงขึ้นของมันสามารถแก้ไขปัญหานี้ได้ แต่จะไม่ช่วยได้เพราะสตริงนั้นยังคงถูกแปลงเป็น Datetime แต่ถ้าหล่อจะมีการเพิ่มcast(2015-07-27 23:59:59.999' as datetime2)นี้ทำงานได้ดี

หล่อและแปลง

Cast สามารถแปลงค่าที่มีสูงสุด 3 หลักเป็น Datetime หรือสูงสุด 9 หลักเป็น Datetime2 หรือ Time และปัดเศษเป็นความแม่นยำที่ถูกต้อง

เป็นที่น่าสังเกตว่า Cast of Datetime2 และ Time2 อาจให้ผลลัพธ์ที่แตกต่าง:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) เปิดตัว 2015-05-03 00: 00: 00.0000000 (สำหรับมูลค่าที่มากกว่า 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

การเรียงลำดับของการแก้ไขปัญหา datetime จะมีการเพิ่มขึ้น 0, 3 และ 7 แม้ว่ามันจะยังดีกว่าเสมอที่จะมองหาวันที่ก่อนที่ 1 นาโนวินาทีที่สองของวันถัดไป (เสมอ 0: 00: 00.000)

Source MSDN: datetime (Transact-SQL)


6

มันกำลังปัดเศษ

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 ทุกตัวละคร / รอบเป็น. 997

ควรใช้

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

หรือ

where cast(posted_date as date) = '2015-07-27'

ดูความแม่นยำในลิงค์นี้
รายงานเป็น. 000, .003, .007 เสมอ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.