sql >> Database teknologi >  >> RDS >> Sqlserver

Hvordan fjerner man tidsdelen af ​​en datetime-værdi (SQL-server)?

SQL Server 2008 og nyere

I SQL Server 2008 og nyere er den hurtigste måde selvfølgelig Convert(date, @date) . Dette kan castes tilbage til en datetime eller datetime2 hvis det er nødvendigt.

Hvad er virkelig bedst i SQL Server 2005 og ældre?

Jeg har set inkonsekvente påstande om, hvad der er hurtigst til at afkorte tiden fra en dato i SQL Server, og nogle mennesker sagde endda, at de testede, men min erfaring har været anderledes. Så lad os lave nogle mere strenge tests og lade alle have scriptet, så hvis jeg laver fejl, kan folk rette mig.

Float-konverteringer er ikke nøjagtige

For det første ville jeg holde mig væk fra at konvertere datetime at float , fordi den ikke konverterer korrekt. Det kan godt være, du slipper af sted med at udføre tidsfjernelse præcist, men jeg synes, det er en dårlig idé at bruge det, fordi det implicit kommunikerer til udviklere, at dette er en sikker operation, og det er det ikke . Tag et kig:

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
 

Dette er ikke noget, vi bør lære folk i vores kode eller i vores eksempler online.

Desuden er det ikke engang den hurtigste måde!

Bevis – præstationstest

Hvis du selv ønsker at udføre nogle tests for at se, hvordan de forskellige metoder virkelig holder sig, så skal du bruge dette opsætningsscript for at køre testene længere nede:

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
 

Bemærk, at dette opretter en tabel på 427,57 MB i din database og vil tage noget i retning af 15-30 minutter at køre. Hvis din database er lille og indstillet til 10 % vækst, vil det tage længere tid, end hvis du først har størrelsen stor nok.

Nu til det faktiske præstationstestscript. Bemærk venligst, at det er formålstjenligt ikke at returnere rækker tilbage til klienten, da dette er vanvittigt dyrt på 26 millioner rækker og ville skjule ydeevneforskellene mellem metoderne.

Ydeevneresultater

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;
 

Nogle vandreanalyse

Nogle bemærkninger om dette. Først og fremmest, hvis du bare udfører en GROUP BY eller en sammenligning, er der ingen grund til at konvertere tilbage til datetime . Så du kan spare noget CPU ved at undgå det, medmindre du har brug for den endelige værdi til visningsformål. Du kan endda GRUPPE BY den ukonverterede værdi og kun placere konverteringen i SELECT-sætningen:

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

Se også, hvordan de numeriske konverteringer kun tager lidt længere tid at konvertere tilbage til datetime , men varchar konvertering næsten fordobles? Dette afslører den del af CPU'en, der er afsat til datoberegning i forespørgslerne. Der er dele af CPU-bruget, der ikke involverer datoberegning, og dette ser ud til at være noget tæt på 19875 ms i ovenstående forespørgsler. Så kræver konverteringen et ekstra beløb, så hvis der er to konverteringer, bliver det beløb brugt cirka to gange.

Flere undersøgelser afslører, at sammenlignet med Convert(, 112) , Convert(, 101) forespørgslen har nogle ekstra CPU-udgifter (da den bruger en længere varchar ?), fordi den anden konvertering tilbage til date koster ikke så meget som den første konvertering til varchar , men med Convert(, 112) det er tættere på de samme 20.000 ms CPU-basisomkostninger.

Her er de beregninger på CPU-tiden, som jeg brugte til ovenstående analyse:

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
  • runde er CPU-tiden for en returflyvning tilbage til datetime .

  • enkelt er CPU-tid for en enkelt konvertering til den alternative datatype (den, der har den bivirkning, at tidsdelen fjernes).

  • base er beregningen af ​​at trække fra single forskellen mellem de to påkaldelser:single - (round - single) . Det er et boldspil, der antager konverteringen til og fra den datatype og datetime er omtrent det samme i begge retninger. Det ser ud til, at denne antagelse ikke er perfekt, men den er tæt på, fordi værdierne alle er tæt på 20.000 ms med kun én undtagelse.

En mere interessant ting er, at basisomkostningerne er næsten lig med den enkelte Convert(date) metode (som skal være næsten 0 omkostninger, da serveren internt kan udtrække heltalsdagsdelen lige ud af de første fire bytes af datetime datatype).

Konklusion

Så hvordan det ser ud er, at den envejs varchar konverteringsmetoden tager ca. 1,8 μs og enkeltvejs DateDiff metode tager omkring 0,18 μs. Jeg baserer dette på den mest konservative "basis CPU"-tid i min test på 18458 ms i alt for 25.920.000 rækker, så 23218 ms / 25920000 =0,18 μs. Den tilsyneladende 10x forbedring virker som meget, men den er ærlig talt ret lille, indtil du har at gøre med hundredtusindvis af rækker (617.000 rækker =1 sekunds besparelse).

Selv givet denne lille absolutte forbedring, er DateAdd efter min mening metode vinder, fordi det er den bedste kombination af ydeevne og klarhed. Svaret, der kræver et "magisk tal" på 0.50000004 kommer til at bide nogen en dag (fem nuller eller seks???), og det er sværere at forstå.

Yderligere bemærkninger

Når jeg får lidt tid, vil jeg ændre 0.50000004 til '12:00:00.003' og se hvordan det gør. Den konverteres til den samme datetime værdi, og jeg finder det meget nemmere at huske.

For de interesserede blev ovenstående test kørt på en server, hvor @@Version returnerer følgende:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) Jul 9 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Standard Edition på Windows NT 5.2 (Build 3790:Service Pack 2)



  1. Topfejl at undgå i MySQL-replikering

  2. Hvorfor bruger SQL Server indeksscanning i stedet for indekssøgning, når WHERE-udtrykket indeholder parametriserede værdier

  3. Brug af strace som et DG40DBC Debugging Tool på Linux

  4. Introduktion til PL/SQL VARRAY'er i Oracle-databasen