จะลบส่วนเวลาของค่าวันที่และเวลา (SQL Server) ได้อย่างไร


85

นี่คือสิ่งที่ฉันใช้:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

ฉันคิดว่าอาจมีวิธีที่ดีกว่าและสง่างามกว่านี้

ข้อกำหนด:

  • จะต้องเร็วที่สุดเท่าที่จะเป็นไปได้ (ยิ่งหล่อน้อยยิ่งดี)
  • ผลลัพธ์สุดท้ายจะต้องเป็นdatetimeประเภทไม่ใช่สตริง

คำตอบ:


116

SQL Server 2008 ขึ้นไป

ใน SQL Server 2008 ขึ้นไปวิธีที่เร็วที่สุดคือConvert(date, @date). ซึ่งสามารถส่งกลับไปที่ a datetimeหรือdatetime2ถ้าจำเป็น

อะไรคือสิ่งที่ดีที่สุดใน SQL Server 2005 และเก่ากว่า

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

การแปลงลอยไม่ถูกต้อง

อันดับแรกฉันจะอยู่ห่างจากการแปลงdatetimeเป็นfloatเพราะมันแปลงไม่ถูกต้อง คุณอาจจะได้รับไปกับการทำสิ่งที่เวลากำจัดอย่างถูกต้อง แต่ฉันคิดว่ามันเป็นความคิดที่ดีที่จะใช้มันเพราะมันโดยปริยายสื่อสารกับนักพัฒนาที่ว่านี้คือการดำเนินการที่ปลอดภัยและมันไม่ได้เป็น ลองดูสิ:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

นี่ไม่ใช่สิ่งที่เราควรสอนผู้คนในโค้ดของเราหรือในตัวอย่างของเราทางออนไลน์

นอกจากนี้ยังไม่ใช่วิธีที่เร็วที่สุด!

หลักฐาน - การทดสอบประสิทธิภาพ

หากคุณต้องการทำการทดสอบด้วยตัวเองเพื่อดูว่าวิธีการต่างๆทำอย่างไรคุณจะต้องใช้สคริปต์การตั้งค่านี้เพื่อเรียกใช้การทดสอบต่อไป:

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

โปรดทราบว่าสิ่งนี้จะสร้างตาราง 427.57 MB ในฐานข้อมูลของคุณและจะใช้เวลาประมาณ 15-30 นาทีในการรัน หากฐานข้อมูลของคุณมีขนาดเล็กและตั้งค่าการเติบโตเป็น 10% จะใช้เวลานานกว่าถ้าคุณมีขนาดใหญ่พอก่อน

ตอนนี้สำหรับสคริปต์การทดสอบประสิทธิภาพจริง โปรดทราบว่ามีจุดประสงค์ที่จะไม่ส่งคืนแถวกลับไปยังไคลเอนต์เนื่องจากมีราคาแพงมากใน 26 ล้านแถวและจะซ่อนความแตกต่างของประสิทธิภาพระหว่างวิธีการ

ผลการดำเนินงาน

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

การวิเคราะห์การเสี่ยงโชค

หมายเหตุเกี่ยวกับเรื่องนี้ datetimeครั้งแรกของทั้งหมดถ้าเพียงการดำเนินการจำแนกตามกลุ่มหรือการเปรียบเทียบที่ไม่ต้องมีการแปลงกลับไป ดังนั้นคุณสามารถประหยัด CPU บางส่วนได้โดยหลีกเลี่ยงสิ่งนั้นเว้นแต่คุณจะต้องการค่าสุดท้ายสำหรับการแสดงผล คุณยังสามารถจัดกลุ่มตามค่าที่ไม่แปลงกลับและใส่การแปลงในส่วนคำสั่ง SELECT เท่านั้น:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

นอกจากนี้ดูว่าการแปลงตัวเลขใช้เวลาเพียงเล็กน้อยในการแปลงกลับไปเป็นdatetimeอย่างไร แต่varcharConversion เพิ่มขึ้นเกือบสองเท่า? สิ่งนี้แสดงให้เห็นส่วนของ CPU ที่ใช้สำหรับการคำนวณวันที่ในแบบสอบถาม มีบางส่วนของการใช้งาน CPU ที่ไม่เกี่ยวข้องกับการคำนวณวันที่และดูเหมือนว่าจะมีค่าใกล้เคียงกับ 19875 ms ในข้อความค้นหาด้านบน จากนั้น Conversion จะใช้จำนวนเพิ่มขึ้นดังนั้นหากมีการแปลงสองรายการจำนวนนั้นจะถูกใช้ไปประมาณสองเท่า

การตรวจสอบเพิ่มเติมพบว่าเมื่อเทียบกับ Convert(, 112)ที่Convert(, 101)แบบสอบถามมีค่าใช้จ่ายบางอย่างเพิ่มเติม CPU (ตั้งแต่จะใช้อีกต่อไปvarchar?) เพราะการแปลงกลับมาที่สองที่จะdateไม่เสียค่าใช้จ่ายมากที่สุดเท่าที่แปลงเริ่มต้นvarcharแต่มีConvert(, 112)มันอยู่ใกล้กับที่เหมือนกัน 20000 ms ต้นทุนฐาน CPU

นี่คือการคำนวณเกี่ยวกับเวลา CPU ที่ฉันใช้สำหรับการวิเคราะห์ข้างต้น:

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • รอบคือเวลา CPU datetimeสำหรับการกลับมาเดินทางไปรอบ

  • singleคือเวลาของ CPU สำหรับการแปลงครั้งเดียวเป็นประเภทข้อมูลทางเลือก (ประเภทที่มีผลข้างเคียงจากการลบส่วนเวลาออก)

  • ฐานคือการคำนวณการลบออกจากsingleความแตกต่างระหว่างการเรียกสองครั้ง:single - (round - single)ความแตกต่างระหว่างสองสวดนี้:มันคือรูป ballpark ที่ถือว่าการแปลงเป็นและจากประเภทข้อมูลนั้นและdatetimeใกล้เคียงกันในทิศทางใดทิศทางหนึ่ง ดูเหมือนว่าสมมติฐานนี้จะไม่สมบูรณ์แบบ แต่ใกล้เคียงเพราะค่าทั้งหมดใกล้เคียงกับ 20000 ms โดยมีข้อยกเว้นเพียงข้อเดียว

สิ่งที่น่าสนใจอีกประการหนึ่งก็คือต้นทุนฐานนั้นเกือบจะเท่ากับราคาเดียว Convert(date)วิธีการ (ซึ่งต้องมีต้นทุนเกือบ 0 เนื่องจากเซิร์ฟเวอร์สามารถแยกส่วนวันจำนวนเต็มออกจากสี่ไบต์แรกของdatetimeประเภทข้อมูลได้ภายใน)

สรุป

สิ่งที่ดูเหมือนคือทิศทางเดียว varcharวิธีการแปลงใช้เวลาประมาณ 1.8 s และDateDiffวิธีทิศทางเดียวใช้เวลาประมาณ 0.18 μs ฉันใช้เวลา "CPU พื้นฐาน" ที่อนุรักษ์นิยมที่สุดในการทดสอบรวม ​​18458 ms สำหรับ 25,920,000 แถวดังนั้น 23218 ms / 25920000 = 0.18 μs การปรับปรุง 10x ที่เห็นได้ชัดดูเหมือนจะมาก แต่ก็ค่อนข้างเล็กมากจนกระทั่งคุณต้องจัดการกับแถวนับแสน (617k แถว = ประหยัด 1 วินาที)

แม้จะได้รับการปรับปรุงที่สมบูรณ์แบบเล็กน้อยนี้ในความคิดของฉันDateAddวิธีนี้ก็ชนะเพราะเป็นการผสมผสานที่ดีที่สุดของประสิทธิภาพและความชัดเจน คำตอบที่ต้องใช้ "เลขวิเศษ" 0.50000004กำลังจะกัดใครสักคนในสักวันหนึ่ง (ศูนย์ห้าหรือหก ???) แถมยังเข้าใจยากกว่าอีกด้วย

หมายเหตุเพิ่มเติม

เมื่อได้เวลาฉันจะเปลี่ยน0.50000004เป็น'12:00:00.003'และดูว่ามันเป็นอย่างไร มันถูกแปลงให้เหมือนกันdatetimeค่าและฉันจำได้ง่ายกว่ามาก

สำหรับผู้ที่สนใจการทดสอบข้างต้นถูกเรียกใช้บนเซิร์ฟเวอร์โดยที่ @@ Version จะส่งคืนสิ่งต่อไปนี้:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 9 กรกฎาคม 2551 14:43:34 ลิขสิทธิ์ (c) 1988-2008 Microsoft Corporation Standard Edition บน Windows NT 5.2 (รุ่น 3790: Service Pack 2)


1
+1 คุณได้ทดสอบ SQL Server เวอร์ชันใดบ้าง
Martin Smith

1
ดูเหมือนว่าคุณจะมีเพียงครั้งเดียวและรอบข้างหลังในตารางของคุณ นอกจากนี้เวลาที่ใช้charแทนจะมีความแตกต่างกันvarcharหรือไม่?
Gabe

1
@Gabe ขอบคุณแก้ไข Char ดูเหมือนจะเหมือนกับ varchar ทุกประการ
ErikE

ใน Oracle มีselect round(sysdate) from dualและเราต้องการสิ่งนั้นใน Sql Server อย่างแน่นอน
Denis Valeev

3
@Roman ถ้าคุณกำลังทำงานกับ SQL Server 2008 ขึ้นไปใช่การแปลงเป็นdateชนิดข้อมูลจะเร็วที่สุดดังที่แสดงในการทดสอบของฉันด้านบน
ErikE

30

SQL Server 2008 มีชนิดข้อมูลวันที่ใหม่และทำให้ปัญหานี้ง่ายขึ้นเพื่อ:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

1
ฉันป้อน 0218 ผิดพลาดแทนที่จะเป็นปี 2018 เป็นปีและDATEADD(DATEDIFF())วิธีการตัดส่วนเวลาทำให้เกิดข้อยกเว้น เมื่อฉันส่งผลลัพธ์กลับไปที่datetime2วิธีการของคุณได้ผลดีselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
Bernhard Döbler

18

Itzik Ben-Gan ในการคำนวณ DATETIME ตอนที่ 1 (นิตยสาร SQL Server, กุมภาพันธ์ 2550) แสดงวิธีการแปลงดังกล่าวสามวิธี ( ช้าที่สุดถึงเร็วที่สุดความแตกต่างระหว่างวิธีที่สองและสามมีขนาดเล็ก):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

ผู้อ่านแนะนำเทคนิคของคุณ (หล่อให้ลอย ) ในนิตยสารฉบับเดือนเมษายน ตามที่เขาพูดมันมีประสิทธิภาพเทียบเท่ากับเทคนิคที่สองที่นำเสนอข้างต้น


1
ในความคิดของฉันการหล่อให้ลอยนั้นไม่ดีที่สุด โปรดดูคำตอบของฉัน
ErikE

1
@Emtucifor ผมเห็นว่าวิธีการที่ 3 ปิดบังมากเพราะของ0.50000004ค่า แต่ก็เป็นหนึ่งที่เร็วที่สุดและการทดสอบของคุณยืนยันว่า ดังนั้นจึงเป็นไปตามข้อกำหนดที่รวดเร็วที่สุด
Marek Grzenkowicz

1
@Emtucifor นอกจากนี้ที่นี่คือสิ่งที่บทความฉันเชื่อมโยงกล่าวเกี่ยวกับ0.50000004ค่า: แม้ว่าการแสดงออกนี้เป็นระยะสั้น (และมีประสิทธิภาพที่สุดเท่าที่ผมจะแสดงให้เห็นในไม่ช้า) ผมต้องบอกว่าผมรู้สึกไม่สบายใจกับมัน ฉันไม่แน่ใจว่าฉันสามารถใช้นิ้วชี้ได้ว่าทำไม - อาจเป็นเพราะมันเป็นเทคนิคเกินไปและคุณไม่เห็นตรรกะที่เกี่ยวข้องกับวันที่และเวลาในนั้น
Marek Grzenkowicz

2
ถ้าเราจะใช้วิธีนี้ฉันจะชอบSELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)แทนเพราะมันมีความหมายสำหรับฉันและ imo จำง่ายกว่ามาก
ErikE

6
นี่คือตอนที่เร็วที่สุดใน SQL Convert(date, GetDate())2008:
ErikE

12

ของคุณCAST- FLOOR-CASTดูเหมือนจะเป็นวิธีที่ดีที่สุดแล้วอย่างน้อยก็ใน MS SQL Server 2005

โซลูชันอื่น ๆ ที่ฉันเคยเห็นมีการแปลงสตริงเช่นSelect Convert(varchar(11), getdate(),101)ในนั้นซึ่งช้าลงโดยปัจจัย 10


1
เราใช้วิธีการที่ Michael Stum แนะนำในผลิตภัณฑ์ของเราและได้ผลเหมือนมีเสน่ห์
Chris Roberts

3
นี่ไม่ใช่วิธีที่ดีที่สุดเลยสักนิด โปรดดูคำตอบของฉันในหน้าเดียวกันนี้
ErikE


1

SQL2005: ฉันแนะนำให้ใช้ cast แทน dateadd ตัวอย่างเช่น,

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

โดยเฉลี่ยเร็วขึ้นประมาณ 10% สำหรับชุดข้อมูลของฉันมากกว่า

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(และการส่งเป็น smalldatetime ก็ยังเร็วกว่า)

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