Se ottieni risultati davvero strani quando usi DATEDIFF()
funzione in SQL Server e sei convinto che la funzione contenga un bug, non strapparti ancora i capelli. Probabilmente non è un bug.
Ci sono scenari in cui i risultati prodotti da questa funzione possono essere piuttosto stravaganti. E se non capisci come funziona effettivamente la funzione, i risultati sembreranno completamente sbagliati.
Speriamo che questo articolo possa aiutare a chiarire come il DATEDIFF()
la funzione è progettata per funzionare e fornire alcuni scenari di esempio in cui i risultati potrebbero non essere come ti aspetteresti.
Esempio 1:365 giorni non sono sempre un anno
Domanda: Quando sono 365 giorni non un anno?
Risposta: Quando si utilizza DATEDIFF()
certo!
Ecco un esempio in cui utilizzo DATEDIFF()
per restituire il numero di giorni tra due date, quindi il numero di anni tra le stesse due date.
DECLARE @startdate datetime2 = '2016-01-01 00:00:00.0000000', @enddate datetime2 = '2016-12-31 23:59:59.9999999'; SELECT DATEDIFF(day, @startdate, @enddate) Days, DATEDIFF(year, @startdate, @enddate) Years;
Risultato:
+--------+---------+ | Days | Years | |--------+---------| | 365 | 0 | +--------+---------+
Se ritieni che questo risultato sia sbagliato e che DATEDIFF()
ovviamente ha un bug, continua a leggere:non tutto è come sembra.
Che ci crediate o no, questo è in realtà il risultato atteso. Questo risultato è esattamente in accordo con come DATEDIFF()
è progettato per funzionare.
Esempio 2 – 100 nanosecondi =1 anno?
Prendiamo l'altro modo.
DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', @enddate datetime2 = '2017-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Risultati (mostrati con output verticale):
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 1 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Ci sono solo cento nanosecondi (.0000001 secondo) di differenza tra le due date/ora, ma otteniamo esattamente lo stesso risultato per ogni datapart, eccetto i nanosecondi.
Come può accadere? Come può essere una differenza di 1 microsecondo e 1 anno di differenza contemporaneamente? Per non parlare di tutte le date nel mezzo?
Potrebbe sembrare una follia, ma neanche questo è un bug. Questi risultati sono esattamente in accordo con come DATEDIFF()
dovrebbe funzionare.
E per rendere le cose ancora più confuse, potremmo ottenere risultati diversi a seconda del tipo di dati. Ma ci arriveremo presto. Per prima cosa diamo un'occhiata a come il DATEDIFF()
la funzione funziona davvero.
La vera definizione di DATEDIFF()
Il motivo per cui otteniamo i risultati è perché DATEDIFF()
funzione è definita come segue:
Questa funzione restituisce il conteggio (come valore intero con segno) dei limiti di datepart specificati superati tra la data di inizio specificata e data di fine .
Prestare particolare attenzione alle parole "confini datapart attraversati". Questo è il motivo per cui otteniamo i risultati degli esempi precedenti. È facile presumere che DATEDIFF()
utilizza il tempo trascorso per i suoi calcoli, ma non lo fa. Utilizza il numero di limiti di datepart superati.
Nel primo esempio, le date non hanno oltrepassato i limiti delle parti dell'anno. L'anno della prima data era esattamente lo stesso dell'anno della seconda data. Nessun confine è stato superato.
Nel secondo esempio, abbiamo avuto lo scenario opposto. Le date hanno superato ogni limite di datepart almeno una volta (100 volte per nanosecondi).
Esempio 3:un risultato diverso per la settimana
Ora, facciamo finta che sia passato un anno intero. Ed eccoci esattamente un anno dopo con i valori di data/ora, tranne per il fatto che i valori dell'anno sono aumentati di uno.
Dovremmo ottenere gli stessi risultati, giusto?
DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', @enddate datetime2 = '2018-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Risultati:
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 0 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Sbagliato.
La maggior parte sono uguali, ma questa volta la settimana ha restituito 0
.
Eh?
Ciò è accaduto perché le date di input hanno lo stesso calendario settimana valori. È successo che le date scelte per esempio 2 avessero valori di settimana di calendario diversi.
Per essere più specifici, l'esempio 2 ha superato i limiti delle parti settimanali che vanno da "2016-12-31" a "2017-01-01". Questo perché l'ultima settimana del 2016 è terminata il 31-12-2016 e la prima settimana del 2017 è iniziata il 01-01-2017 (domenica).
Ma nell'esempio 3, la prima settimana del 2018 è iniziata effettivamente alla nostra data di inizio del 31-12-2017 (domenica). La nostra data di fine, essendo il giorno successivo, cadeva nella stessa settimana. Pertanto, non sono stati superati i limiti delle parti settimanali.
Questo ovviamente presuppone che la domenica sia il primo giorno di ogni settimana. A quanto pare, il DATEDIFF()
funzione fa supponiamo che la domenica sia il primo giorno della settimana. Ignora persino il tuo SET DATEFIRST
impostazione (questa impostazione consente di specificare in modo esplicito quale giorno è considerato il primo giorno della settimana). Il ragionamento di Microsoft per ignorare SET DATEFIRST
è che garantisce il DATEDIFF()
la funzione è deterministica. Ecco una soluzione alternativa se questo è un problema per te.
Quindi, in poche parole, i tuoi risultati potrebbero sembrare "sbagliati" per qualsiasi parte di date a seconda delle date / orari. I tuoi risultati possono sembrare molto sbagliati quando usi la parte settimanale. E potrebbero sembrare ancora più sbagliati se usi un SET DATEFIRST
valore diverso da 7 (per domenica) e ti aspetti DATEDIFF()
per onorarlo.
Ma i risultati non sono sbagliati e non è un bug. È solo più un "gotcha" per coloro che non sanno come funziona effettivamente la funzione.
Tutti questi trucchi si applicano anche a DATEDIFF_BIG()
funzione. Funziona come DATEDIFF()
con l'eccezione che restituisce il risultato come bigint con segno (al contrario di un int per DATEDIFF()
).
Esempio 4:i risultati dipendono dal tipo di dati
Potresti anche ottenere risultati imprevisti a causa del tipo di dati che utilizzi per le date di input. I risultati spesso differiranno a seconda del tipo di dati delle date di input. Ma non puoi incolpare DATEDIFF()
per questo, in quanto è puramente dovuto alle capacità e ai limiti dei vari tipi di dati. Non puoi aspettarti di ottenere risultati di alta precisione da un valore di input di bassa precisione.
Ad esempio, ogni volta che la data di inizio o di fine ha un smalldatetime valore, i secondi e i millisecondi restituiranno sempre 0. Questo perché smalldatetime il tipo di dati è accurato solo al minuto.
Ecco cosa succede se passiamo all'esempio 2 per utilizzare smalldatetime invece di datetime2 :
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Risultato:
Year | 0 Quarter | 0 Month | 0 DOY | 0 Day | 0 Week | 0 Hour | 0 Minute | 0 Second | 0 Millisecond | 0 Microsecond | 0 Nanosecond | 0
Il motivo per cui sono tutti zero è perché entrambe le date di input sono in realtà identiche:
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT @startdate 'Start Date', @enddate 'End Date';
Risultato:
+---------------------+---------------------+ | Start Date | End Date | |---------------------+---------------------| | 2017-01-01 00:00:00 | 2017-01-01 00:00:00 | +---------------------+---------------------+
I limiti di smalldatetime il tipo di dati ha causato l'arrotondamento per eccesso dei secondi, che ha quindi causato un effetto flusso e tutto è stato arrotondato per eccesso. Anche se non ottieni valori di input identici, potresti comunque ottenere un risultato imprevisto a causa del tipo di dati che non fornisce la precisione di cui hai bisogno.