Perché il mio tempo di ricerca delle query non corrisponde?


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Ma il risultato contiene un record pubblicato oggi_data: 28-07-2015. Il mio server di database non è nel mio paese. Qual è il problema ?

Risposte:


16

Dato che stai usando il datetimetipo di dati, devi capire come il server sql arrotonda i dati del datetime.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

inserisci qui la descrizione dell'immagine

Utilizzando la query di seguito, è possibile vedere facilmente il problema dell'arrotondamento del server SQL quando si utilizza il DATETIMEtipo di dati.

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

inserisci qui la descrizione dell'immagine clicca per ingrandire

DATETIME2è in circolazione da SQL Server 2008, quindi inizia a usarlo invece di DATETIME. Per la tua situazione, puoi usare datetime2con precisione di 3 decimali, ad es datetime2(3).

Vantaggi dell'utilizzo datetime2:

  • Supporta fino a 7 posizioni decimali per componente temporale rispetto al datetimesupporto solo di 3 posizioni decimali .. e quindi si vede il problema degli arrotondamenti poiché per impostazione predefinita datetimearrotonda il più vicino .003 secondscon incrementi di .000, .003o .007secondi.
  • datetime2è molto più preciso di datetimee datetime2ti dà il controllo DATEe TIMEal contrario datetime.

Riferimento:


1
gives you control of DATE and TIME as opposed to datetime.cosa significa?
Nurettin,

Ri. usando DateTime2vs DateTime.: a. Per - la - vasta - maggioranza - del - mondo reale - casi d'uso, i vantaggi di DateTime2molto <costi. Vedi: stackoverflow.com/questions/1334143/… b. Questo non è il problema alla radice qui. Vedi il prossimo commento
Tom,

Il problema alla radice qui (come scommetto che la maggior parte degli sviluppatori senior sarà d'accordo) non è una precisione insufficiente nella data-ora di fine inclusiva di un confronto dell'intervallo di date-ora, ma piuttosto l'uso di un periodo inclusivo (vs. esclusivo). È come controllare l'uguaglianza con Pi, c'è sempre la possibilità che uno dei # abbia> o <precisione (cioè cosa succede se datetime3con 70 (contro 7) cifre di precisione aggiunte?). La migliore pratica consiste nell'utilizzare un valore in cui la precisione non ha importanza, ovvero <l' inizio del secondo, minuto, ora o giorno successivo vs. <= la fine del secondo, minuto, ora o giorno precedenti.
Tom,

18

Come molti altri hanno menzionato nei commenti e altre risposte alla tua domanda, il problema principale 2015-07-27 23:59:59.999è stato arrotondato 2015-07-28 00:00:00.000da SQL Server. Secondo la documentazione per DATETIME:

Intervallo di tempo: da 00:00:00 a 23: 59: 59.997

Si noti che l'intervallo di tempo non può mai essere .999. Più in basso nella documentazione specifica le regole di arrotondamento utilizzate da SQL Server per la cifra meno significativa.

Tabella che mostra le regole di arrotondamento

Si noti che la cifra meno significativa può avere solo uno dei tre valori potenziali: "0", "3" o "7".

Esistono diverse soluzioni / soluzioni alternative per questo che è possibile utilizzare.

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

Tra le cinque opzioni che ho presentato sopra, prenderei in considerazione le opzioni 1 e 3 le uniche opzioni praticabili. Trasmettono chiaramente le tue intenzioni e non si romperanno se aggiorni i tipi di dati. Se stai usando SQL Server 2008 o più recente, penso che l'opzione 3 dovrebbe essere il tuo approccio preferito. Ciò è particolarmente vero se è possibile passare dall'uso del DATETIMEtipo di dati a un DATEtipo di dati per la posted_datecolonna.

Per quanto riguarda l'opzione 3, una buona spiegazione su alcuni problemi può essere trovata qui: Cast fino ad oggi è sargable ma è una buona idea?

Non mi piacciono le opzioni 2 e 5 perché i .997secondi frazionari saranno solo un altro numero magico che le persone vorranno "aggiustare". Per alcuni altri motivi per cui BETWEENnon è ampiamente accettato, potresti voler dare un'occhiata a questo post .

Non mi piace l'opzione 4 perché convertire i tipi di dati in una stringa a scopo di confronto mi sembra sporco. Un motivo più qualitativo per evitarlo in SQL Server è che influisce sulla sargibilità, ovvero che non è possibile eseguire una ricerca dell'indice e ciò comporterà spesso prestazioni peggiori .

Per ulteriori informazioni sulla strada giusta e sul modo sbagliato di gestire le query sull'intervallo di date, consulta questo post di Aaron Bertrand .

Nel separarti sarai in grado di mantenere la tua query originale e si comporterebbe come desiderato se cambi la tua posted_datecolonna da a DATETIMEa a DATETIME2(3). Ciò consentirebbe di risparmiare spazio di archiviazione sul server, offrire una maggiore accuratezza con la stessa precisione, essere più conformi agli standard / portatili e consentire di regolare facilmente l'accuratezza / precisione se le esigenze cambieranno in futuro. Tuttavia, questa è solo un'opzione se si utilizza SQL Server 2008 o versioni successive.

Come un po 'di curiosità, la 1/300seconda precisione con DATETIMEsembra essere una sospensione da UNIX per questa risposta StackOverflow . Sybase che ha un'eredità condivisa ha una 1/300seconda precisione simile nei loro tipi di dati DATETIMEeTIME , ma le loro cifre meno significative sono leggermente diverse in "0", "3" e "6". A mio avviso, la 1/300precisione di un secondo e / o 3,33 ms è una decisione architettonica sfortunata poiché il blocco di 4 byte per il tempo nel DATETIMEtipo di dati di SQL Server avrebbe potuto facilmente supportare una precisione di 1 ms.


Sì, ma il "problema principale " principale non sta utilizzando l'opzione 1 (ad es. L'utilizzo di qualsiasi valore finale dell'intervallo inclusivo (vs. esclusivo) in cui la precisione di tipi di dati passati o potenziali futuri potrebbe influire sui risultati). È come controllare l'uguaglianza con Pi, è sempre possibile che uno # abbia> o <precisione (a meno che entrambi non siano pre-arrotondati alla precisione comune più bassa). Cosa succede se datetime3con 70 (vs. 7) cifre di precisione aggiunte? La migliore pratica consiste nell'utilizzare un valore in cui la precisione non ha importanza, ovvero <l'inizio del secondo, minuto, ora o giorno successivo rispetto a <= la fine del secondo, minuto, ora o giorno precedente.
Tom,

9

Conversione implicita

Ho supposto che il tipo di dati di date_date sia Datetime. Tuttavia, non importa se il tipo sull'altro lato è Datetime, Datetime2 o solo Time perché la stringa (Varchar) verrà convertita implicitamente in Datetime.

Con posts_date dichiarata come Datetime2 (o Time), la posted_date <= '2015-07-27 23:59:59.99999'clausola where ha esito negativo poiché purugh 23:59:59.99999è un valore Datetime2 valido, questo non è un valore Datetime valido:

 Conversion failed when converting date and/or time from character string.

Intervallo di tempo per Datetime

L'intervallo di tempo di Datetime è compreso tra 00:00:00 e 23: 59: 59.997. Pertanto 23: 59: 59.999 non è compreso nell'intervallo e deve essere arrotondato per eccesso o per difetto al valore più vicino.

Precisione

Inoltre, i valori Datetime sono arrotondati con incrementi di .000, .003 o .007 secondi. (ad es. 000, 003, 007, 010, 013, 017, 020, ..., 997)

Questo non è il caso del valore 2015-07-27 23:59:59.999compreso in questo intervallo: 2015-07-27 23:59:59.997e 2015-07-28 0:00:00.000.

Questo intervallo corrisponde alle opzioni precedenti e seguenti più vicine, entrambe che terminano con .000, .003 o .007.

Arrotondamento verso l'alto o verso il basso ?

Perché è più vicino al 2015-07-28 0:00:00.000(+1 contro -2) rispetto 2015-07-27 23:59:59.997, la stringa viene arrotondato per eccesso e diventa questo valore Datetime: 2015-07-28 0:00:00.000.

Con un limite superiore come 2015-07-27 23:59:59.998(o .995, .996, .997, .998), sarebbe stato arrotondato per difetto 2015-07-27 23:59:59.997e la query avrebbe funzionato come previsto. Tuttavia non sarebbe stata una soluzione ma solo un valore fortunato.

Tipi di Datetime2 o Time

Datetime2 e Time intervalli di tempo sono 00:00:00.0000000attraverso 23:59:59.9999999con una precisione di 100 ns (l'ultima cifra quando utilizzato con una precisione 7 cifre).

Tuttavia, un intervallo Datetime (3) non è simile all'intervallo Datetime:

  • Datetime 0:0:00.000a23:59:59.997
  • Datetime2 0:0:00.000000000a23:59:59.999

Soluzione

Alla fine è più sicuro cercare le date al di sotto del giorno successivo rispetto alle date al di sotto o uguali a quello che pensi sia l'ultimo frammento di tempo del giorno. Questo principalmente perché sai che il giorno successivo inizia sempre alle 0: 00: 00.000 ma diversi tipi di dati potrebbero non avere lo stesso orario alla fine della giornata:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000ti darà risultati accurati ed è l'opzione migliore
  • <= 2015-07-27 23:59:59.xxx può restituire valori imprevisti quando non è arrotondato per eccesso a ciò che si pensa che dovrebbe essere.
  • La conversione in data e l'utilizzo della funzione dovrebbero essere evitati perché limitano l'uso degli indici

Potremmo pensare che cambiando [data_data] in Datetime2 e la sua maggiore precisione potrebbe risolvere questo problema ma non sarà d'aiuto perché la stringa è ancora convertita in Datetime. Tuttavia, se viene aggiunto un cast cast(2015-07-27 23:59:59.999' as datetime2), questo funziona bene

Trasmetti e converti

Il cast può convertire un valore con un massimo di 3 cifre in Datetime o con un massimo di 9 cifre in Datetime2 o Tempo e arrotondarlo alla precisione corretta.

Va notato che Cast di Datetime2 e Time2 può dare risultati diversi:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) è arrotondato per primo 2015-05-03 00: 00: 00.0000000 (per un valore superiore a 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Risolve il problema che il datetime sta riscontrando con gli incrementi di 0, 3 e 7, sebbene sia sempre meglio cercare le date prima del primo nano secondo del giorno successivo (sempre 0: 00: 00.000).

MSDN di origine: datetime (Transact-SQL)


6

Si sta arrotondando

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 tutto cast / round a .997

Dovrebbe usare

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

o

where cast(posted_date as date) = '2015-07-27'

Vedi la precisione in questo link
Sempre segnalato come .000, .003, .007


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.