Risposte:
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 ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝
Utilizzando la query di seguito, è possibile vedere facilmente il problema dell'arrotondamento del server SQL quando si utilizza il DATETIME
tipo 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
DATETIME2
è in circolazione da SQL Server 2008, quindi inizia a usarlo invece di DATETIME
. Per la tua situazione, puoi usare datetime2
con precisione di 3 decimali, ad es datetime2(3)
.
Vantaggi dell'utilizzo datetime2
:
datetime
supporto solo di 3 posizioni decimali .. e quindi si vede il problema degli arrotondamenti poiché per impostazione predefinita datetime
arrotonda il più vicino .003 seconds
con incrementi di .000
, .003
o .007
secondi.datetime2
è molto più preciso di datetime
e datetime2
ti dà il controllo DATE
e TIME
al contrario datetime
.Riferimento:
DateTime2
vs DateTime
.: a. Per - la - vasta - maggioranza - del - mondo reale - casi d'uso, i vantaggi di DateTime2
molto <costi. Vedi: stackoverflow.com/questions/1334143/… b. Questo non è il problema alla radice qui. Vedi il prossimo commento
datetime3
con 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.
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.000
da 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.
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_date
colonna.
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 .997
secondi frazionari saranno solo un altro numero magico che le persone vorranno "aggiustare". Per alcuni altri motivi per cui BETWEEN
non è 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_date
colonna 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/300
seconda precisione con DATETIMEsembra essere una sospensione da UNIX per questa risposta StackOverflow . Sybase che ha un'eredità condivisa ha una 1/300
seconda precisione simile nei loro tipi di dati DATETIME
eTIME
, ma le loro cifre meno significative sono leggermente diverse in "0", "3" e "6". A mio avviso, la 1/300
precisione 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.
datetime3
con 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.
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.999
compreso in questo intervallo: 2015-07-27 23:59:59.997
e 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.997
e 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.0000000
attraverso 23:59:59.9999999
con 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:
0:0:00.000
a23:59:59.997
0:0:00.000000000
a23: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.000
ti 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.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.9999999Risolve 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)
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
select * from A where date(posted_date) = '2015-07-27'
'DATE' is not a recognized built-in function name.
gives you control of DATE and TIME as opposed to datetime.
cosa significa?