sql >> Databasteknik >  >> RDS >> Sqlserver

Hur tar man bort tidsdelen av ett datetime-värde (SQL-server)?

SQL Server 2008 och senare

I SQL Server 2008 och senare är naturligtvis det snabbaste sättet Convert(date, @date) . Detta kan skickas tillbaka till en datetime eller datetime2 vid behov.

Vad är egentligen bäst i SQL Server 2005 och äldre?

Jag har sett inkonsekventa påståenden om vad som är snabbast för att trunkera tiden från ett datum i SQL Server, och vissa personer sa till och med att de gjorde tester, men min erfarenhet har varit annorlunda. Så låt oss göra lite mer stränga tester och låta alla ha skriptet så att om jag gör några misstag kan folk rätta mig.

Flytande omvandlingar är inte korrekta

Först skulle jag hålla mig borta från att konvertera datetime att float , eftersom den inte konverterar korrekt. Du kanske kommer undan med att göra tidsborttagningen korrekt, men jag tror att det är en dålig idé att använda det eftersom det implicit kommunicerar till utvecklare att detta är en säker operation och det är det inte . Ta en titt:

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

Detta är inte något vi borde lära människor i vår kod eller i våra exempel online.

Dessutom är det inte ens det snabbaste sättet!

Bevis – prestandatestning

Om du vill utföra några tester själv för att se hur de olika metoderna verkligen fungerar, behöver du det här installationsskriptet för att köra testen längre ner:

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

Observera att detta skapar en tabell på 427,57 MB i din databas och att det tar ungefär 15-30 minuter att köra. Om din databas är liten och inställd på 10 % tillväxt kommer det att ta längre tid än om du gör en tillräckligt stor först.

Nu till själva prestationstestningsskriptet. Observera att det är ändamålsenligt att inte returnera rader tillbaka till klienten eftersom detta är galet dyrt på 26 miljoner rader och skulle dölja prestandaskillnaderna mellan metoderna.

Prestanda resultat

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;

Några tråkiga analyser

Några anteckningar om detta. Först och främst, om du bara utför en GROUP BY eller en jämförelse, behöver du inte konvertera tillbaka till datetime . Så du kan spara lite CPU genom att undvika det, om du inte behöver det slutliga värdet för visningsändamål. Du kan till och med GROUP BY det okonverterade värdet och sätta konverteringen endast i SELECT-satsen:

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 också hur de numeriska omvandlingarna bara tar något längre tid att konvertera tillbaka till datetime , men varchar konvertering nästan fördubblas? Detta avslöjar den del av CPU:n som är ägnad åt datumberäkning i frågorna. Det finns delar av CPU-användningen som inte involverar datumberäkning, och detta verkar vara något nära 19875 ms i ovanstående frågor. Sedan tar konverteringen ytterligare ett belopp, så om det finns två omvandlingar används det beloppet ungefär två gånger.

Fler undersökningar visar att jämfört med Convert(, 112) , Convert(, 101) frågan har en viss extra CPU-kostnad (eftersom den använder en längre varchar ?), eftersom den andra konverteringen tillbaka till date kostar inte lika mycket som den första konverteringen till varchar , men med Convert(, 112) det är närmare samma 20 000 ms CPU-baskostnad.

Här är dessa beräkningar på CPU-tiden som jag använde för ovanstående analys:

     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
  • runda är CPU-tiden för en tur och retur tillbaka till datetime .

  • singel är CPU-tid för en enda konvertering till den alternativa datatypen (den som har bieffekten att tidsdelen tas bort).

  • bas är beräkningen av att subtrahera från single skillnaden mellan de två anropen:single - (round - single) . Det är en bollplank som antar konverteringen till och från den datatypen och datetime är ungefär densamma i båda riktningarna. Det verkar som om detta antagande inte är perfekt men är nära eftersom värdena alla är nära 20 000 ms med bara ett undantag.

En mer intressant sak är att baskostnaden är nästan lika med den enda Convert(date) metod (som måste vara nästan 0 kostnad, eftersom servern internt kan extrahera heltalsdagsdelen direkt ur de första fyra byten av datetime datatyp).

Slutsats

Så vad det ser ut är att enkelriktad varchar konverteringsmetoden tar cirka 1,8 μs och enkelriktad DateDiff metoden tar cirka 0,18 μs. Jag baserar detta på den mest konservativa "bas-CPU"-tiden i mina tester på totalt 18458 ms för 25 920 000 rader, så 23 218 ms / 25920000 =0,18 μs. Den uppenbara 10x förbättringen verkar vara mycket, men den är ärligt talat ganska liten tills du har att göra med hundratusentals rader (617 000 rader =1 sekunds besparing).

Även med tanke på denna lilla absoluta förbättring, enligt min mening, DateAdd metoden vinner eftersom det är den bästa kombinationen av prestanda och tydlighet. Svaret som kräver ett "magiskt tal" på 0.50000004 kommer att bita någon någon dag (fem nollor eller sex???), plus att det är svårare att förstå.

Ytterligare anteckningar

När jag får lite tid ska jag ändra 0.50000004 till '12:00:00.003' och se hur det gör. Den konverteras till samma datetime värde och jag tycker det är mycket lättare att komma ihåg.

För de intresserade kördes ovanstående tester på en server där @@Version returnerar följande:

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



  1. tomcat7 - jdbc datasource - Detta är mycket sannolikt att skapa en minnesläcka

  2. Använda en vy utan primärnyckel med Entity

  3. Oracle SQL-frågeloggning

  4. Vad är SQL Injection?