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


85

Ecco cosa utilizzo:

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

Penso che potrebbe esserci un modo migliore e più elegante.

Requisiti:

  • Deve essere il più veloce possibile (minore è il lancio, meglio è).
  • Il risultato finale deve essere un datetimetipo, non una stringa.

Risposte:


116

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 datetimeo datetime2se necessario.

Cosa c'è di veramente 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ù rigorosi e lasciamo che tutti abbiano la sceneggiatura, così se faccio degli errori le persone possono correggermi.

Le conversioni flottanti non sono accurate

Innanzitutto, starei lontano dalla conversione datetimein float, perché non si converte correttamente. Potresti cavartela facendo la cosa della rimozione del tempo in modo accurato, ma penso che sia una cattiva idea usarla perché comunica implicitamente agli sviluppatori che questa è un'operazione sicura e non lo è . Guarda:

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 i diversi metodi si accumulano, 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 da 427,57 MB nel tuo database e richiederà qualcosa come 15-30 minuti per l'esecuzione. Se il database è piccolo e impostato su una crescita del 10%, ci vorrà più tempo rispetto a quando si dimensiona prima abbastanza grande.

Passiamo ora allo script di test delle prestazioni effettivo. Si noti che è utile non restituire le righe al client poiché questo è incredibilmente 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 vaganti

Alcune note su questo. Prima di tutto, se si esegue solo un GROUP BY o un confronto, non è necessario riconvertirsi a datetime. Quindi puoi risparmiare un po 'di CPU evitandolo, 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, vedi come le conversioni numeriche impiegano solo un po 'più di tempo per riconvertirsi datetime, ma la varcharconversione 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 esame più approfondito rivela che rispetto a Convert(, 112), la Convert(, 101)query ha qualche spesa aggiuntiva per la CPU (dato che usa un più lungo varchar?), Perché la seconda conversione a datenon costa quanto la conversione iniziale a varchar, ma con Convert(, 112)essa è più vicina allo stesso 20000 ms costo base della CPU.

Ecco quei calcoli sul tempo della CPU che ho usato per l'analisi 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
  • round è 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).

  • di base è il calcolo della sottraendo singlela differenza tra le due invocazioni: single - (round - single). È una cifra datetimeapprossimativa che presuppone la conversione da e verso quel tipo di dati ed è approssimativamente uguale 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 di base è quasi uguale al Convert(date)metodo singolo (che deve essere quasi pari a 0, poiché il server può estrarre internamente la parte intera del giorno dai primi quattro byte del datetimetipo di dati).

Conclusione

Quindi quello che sembra è che il varcharmetodo di conversione unidirezionale impiega circa 1,8 μs e il DateDiffmetodo unidirezionale impiega circa 0,18 μs. Sto basando questo sul tempo di "CPU di base" più conservativo nei miei test di 18458 ms totali per 25.920.000 righe, quindi 23218 ms / 25920000 = 0,18 μs. L'apparente miglioramento di 10 volte sembra molto, ma francamente è piuttosto piccolo finché non si ha a che fare con centinaia di migliaia di righe (617.000 righe = 1 secondo di risparmio).

Anche dato questo piccolo miglioramento assoluto, secondo me, il DateAddmetodo vince perché è il miglior connubio tra performance e chiarezza. La risposta che richiede un "numero magico" 0.50000004morderà qualcuno un giorno (cinque zeri o sei ???), in più è più difficile da capire.

Note aggiuntive

Quando ricevo un po 'di tempo ho intenzione di cambiare 0.50000004a '12:00:00.003'e vedere come lo fa. Viene convertito allo stesso datetimevalore 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)


1
+1 Su quale versione di SQL Server l'hai provato?
Martin Smith,

1
Sembra che tu abbia un singolo e un giro all'indietro nella tua tabella. Inoltre, c'è qualche differenza di tempo se usi charinvece di varchar?
Gabe

1
@Gabe grazie, risolto. Char sembra essere esattamente lo stesso di varchar.
ErikE

In Oracle c'è select round(sysdate) from duale ne abbiamo sicuramente bisogno in Sql Server.
Denis Valeev

3
@Roman Se stai lavorando con SQL Server 2008 e versioni successive, sì, la conversione al datetipo di dati è più veloce, come mostrato nei miei test sopra.
ErikE

30

SQL Server 2008 ha un nuovo tipo di dati di data e questo semplifica questo problema a:

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

1
Avevo erroneamente inserito 0218 invece del 2018 come anno e il DATEADD(DATEDIFF())metodo per ridurre la parte temporale genera un'eccezione. Quando datetime2select cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
riporto

18

Itzik Ben-Gan in DATETIME Calculations, Part 1 (SQL Server Magazine, febbraio 2007) mostra tre metodi per eseguire tale conversione (dal più lento al più veloce ; la differenza tra il secondo e il terzo metodo è piccola):

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)

La tua tecnica (casting to float ) è suggerita da un lettore nel numero di aprile della rivista. Secondo lui, ha prestazioni paragonabili a quelle della seconda tecnica presentata sopra.


1
Secondo me il casting per galleggiare non è il massimo. Si prega di vedere la mia risposta
ErikE

1
@Emtucifor sono d'accordo che il terzo metodo è molto oscuro a causa del valore 0.50000004 , ma è il più veloce e i tuoi test lo confermano . Pertanto, soddisfa il requisito il più velocemente possibile .
Marek Grzenkowicz

1
@Emtucifor Inoltre, ecco cosa dice l'articolo che ho collegato riguardo al valore 0,50000004 : Sebbene questa espressione sia breve (ed efficiente, come dimostrerò tra poco), devo dire che mi sento a disagio . Non sono sicuro di poter individuare esattamente il motivo, forse perché è troppo tecnico e non puoi vedere la logica relativa al datetime.
Marek Grzenkowicz

2
Se utilizzeremo questo metodo, preferirei SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)invece, poiché significa qualcosa per me ed è molto più facile da ricordare.
ErikE

6
Questo è ora più veloce in SQL 2008: Convert(date, GetDate()).
ErikE

12

Il tuo CAST- FLOOR- CASTsembra già essere il modo ottimale, almeno su MS SQL Server 2005.

Alcune altre soluzioni che ho visto hanno una conversione di stringhe, come Select Convert(varchar(11), getdate(),101)in esse, che è più lenta di un fattore 10.


1
Usiamo il metodo suggerito da Michael Stum in uno dei nostri prodotti e funziona a meraviglia.
Chris Roberts

3
Questo non è il modo ottimale, per un bel po '. Si prega di vedere la mia risposta su questa stessa pagina.
ErikE

4

Per favore prova:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]

1

SQL2005: raccomando cast invece di dateadd. Per esempio,

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

in media circa il 10% più veloce sul mio set di dati, rispetto a

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

(e il casting in smalldatetime era ancora più veloce)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.