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

Come rimuovere la parte temporale di un valore datetime (SQL Server)?

SQL Server 2008 e versioni successive

In SQL Server 2008 e versioni successive, ovviamente il modo più veloce è Convert(date, @date) . Questo può essere restituito a un datetime o datetime2 se necessario.

Che cosa è davvero il migliore in SQL Server 2005 e versioni precedenti?

Ho visto affermazioni incoerenti su ciò che è più veloce per troncare l'ora da una data in SQL Server e alcune persone hanno persino affermato di aver eseguito dei test, ma la mia esperienza è stata diversa. Quindi facciamo dei test più severi e lasciamo che tutti abbiano lo script così se commetto degli errori le persone possono correggermi.

Le conversioni float non sono accurate

Innanzitutto, eviterei di convertire datetime per float , perché non viene convertito correttamente. Potresti cavartela facendo accuratamente la rimozione del tempo, ma penso che sia una cattiva idea usarla perché comunica implicitamente agli sviluppatori che si tratta di un'operazione sicura e non lo è . Dai un'occhiata:

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

Questo non è qualcosa che dovremmo insegnare alle persone nel nostro codice o nei nostri esempi online.

Inoltre, non è nemmeno il modo più veloce!

Prova:test delle prestazioni

Se vuoi eseguire tu stesso alcuni test per vedere come si accumulano davvero i diversi metodi, allora avrai bisogno di questo script di installazione per eseguire i test più in basso:

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

Tieni presente che questo crea una tabella di 427,57 MB nel tuo database e l'esecuzione richiederà circa 15-30 minuti. Se il tuo database è piccolo e impostato su una crescita del 10%, ci vorrà più tempo che se prima dimensioni abbastanza grandi.

Ora per lo script di test delle prestazioni effettivo. Tieni presente che è intenzionale non restituire le righe al client in quanto ciò è estremamente costoso su 26 milioni di righe e nasconderebbe le differenze di prestazioni tra i metodi.

Risultati delle prestazioni

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;

Alcune analisi sconclusionate

Alcune note a riguardo. Prima di tutto, se si esegue solo un GROUP BY o un confronto, non è necessario riconvertire in datetime . Quindi puoi risparmiare un po 'di CPU evitando quello, a meno che tu non abbia bisogno del valore finale per scopi di visualizzazione. Puoi anche GROUP BY il valore non convertito e inserire la conversione solo nella clausola 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)

Inoltre, scopri come le conversioni numeriche impiegano solo un po' più di tempo per riconvertirsi in datetime , ma il varchar la conversione quasi raddoppia? Questo rivela la parte della CPU dedicata al calcolo della data nelle query. Ci sono parti dell'utilizzo della CPU che non implicano il calcolo della data e questo sembra essere qualcosa di vicino a 19875 ms nelle query precedenti. Quindi la conversione richiede un importo aggiuntivo, quindi se ci sono due conversioni, tale importo viene utilizzato circa il doppio.

Un ulteriore esame lo rivela rispetto a Convert(, 112) , il Convert(, 101) query ha una spesa aggiuntiva per la CPU (poiché utilizza un varchar più lungo ?), perché la seconda conversione torna a date non costa quanto la conversione iniziale in varchar , ma con Convert(, 112) è più vicino allo stesso costo di base della CPU di 20000 ms.

Ecco quei calcoli sul tempo della CPU che ho usato per l'analisi di cui sopra:

     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
  • rotondo è il tempo della CPU per un viaggio di andata e ritorno a datetime .

  • single è il tempo della CPU per una singola conversione nel tipo di dati alternativo (quello che ha l'effetto collaterale di rimuovere la porzione di tempo).

  • base è il calcolo della sottrazione da single la differenza tra le due invocazioni:single - (round - single) . È una cifra ballpark che presuppone la conversione da e verso quel tipo di dati e datetime è approssimativamente lo stesso in entrambe le direzioni. Sembra che questa ipotesi non sia perfetta, ma è vicina perché i valori sono tutti vicini a 20000 ms con una sola eccezione.

Un'altra cosa interessante è che il costo base è quasi uguale al singolo Convert(date) metodo (che deve avere un costo quasi 0, poiché il server può estrarre internamente la parte del giorno intero direttamente dai primi quattro byte di datetime tipo di dati).

Conclusione

Quindi quello che sembra è che la singola direzione varchar il metodo di conversione richiede circa 1,8 μs e il DateDiff a direzione singola il metodo richiede circa 0,18 μs. Sto basando questo sul tempo di "CPU di base" più conservativo nel mio test di 18458 ms in totale per 25.920.000 righe, quindi 23218 ms / 25920000 =0,18 μs. L'apparente miglioramento di 10 volte sembra molto, ma francamente è piuttosto piccolo fino a quando non hai a che fare con centinaia di migliaia di righe (617.000 righe =1 secondo di risparmio).

Nonostante questo piccolo miglioramento assoluto, a mio avviso, il DateAdd il metodo vince perché è la migliore combinazione di prestazioni e chiarezza. La risposta che richiede un "numero magico" di 0.50000004 morderà qualcuno un giorno (cinque zeri o sei???), inoltre è più difficile da capire.

Note aggiuntive

Quando ho un po' di tempo cambierò 0.50000004 a '12:00:00.003' e guarda come va. Viene convertito nello stesso datetime valore e lo trovo molto più facile da ricordare.

Per chi fosse interessato, i test di cui sopra sono stati eseguiti su un server dove @@Version restituisce quanto segue:

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