Respuestas:
Como está utilizando el datetimetipo de datos, debe comprender cómo el servidor sql redondea los datos de fecha y hora.
╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
║ 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 ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝
Usando la consulta a continuación, puede ver fácilmente el problema de redondeo que hace el servidor sql cuando usa DATETIME
el tipo de datos.
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
ha existido desde SQL Server 2008, así que comience a usarlo en lugar de DATETIME
. Para su situación, puede utilizar datetime2
con precisión de 3 decimales, por ejemplo datetime2(3)
.
Beneficios de usar datetime2
:
datetime
admitir solo 3 lugares decimales ... y, por lo tanto, ve el problema de redondeo ya que de forma predeterminada datetime
redondea el más cercano .003 seconds
con incrementos de .000
, .003
o .007
segundos.datetime2
es mucho más preciso datetime
y datetime2
le da control DATE
y TIME
no datetime
.Referencia:
DateTime2
vs DateTime
.: a. Para la gran mayoría de los casos de uso en el mundo real , beneficios de DateTime2
Mucho <costos. Ver: stackoverflow.com/questions/1334143/… b. Ese no es el problema raíz aquí. Ver siguiente comentario.
datetime3
agregan 70 (vs. 7) dígitos de precisión?). La mejor práctica es usar un valor donde la precisión no importa, es decir, <el comienzo del siguiente segundo, minuto, hora o día vs. <= el final del segundo, minuto, hora o día anterior.
Como varios otros han mencionado en los comentarios y otras respuestas a su pregunta es el tema central 2015-07-27 23:59:59.999
se redondea al 2015-07-28 00:00:00.000
por SQL Server. Según la documentación para DATETIME:
Rango de tiempo: 00:00:00 a 23: 59: 59.997
Tenga en cuenta que el rango de tiempo nunca puede ser .999
. Más abajo en la documentación, especifica las reglas de redondeo que SQL Server usa para el dígito menos significativo.
Observe que el dígito menos significativo solo puede tener uno de tres valores potenciales: "0", "3" o "7".
Hay varias soluciones / soluciones para esto que puede usar.
-- 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
De las cinco opciones que he presentado anteriormente, consideraría las opciones 1 y 3 como las únicas opciones viables. Transmiten su intención claramente y no se romperán si actualiza los tipos de datos. Si está utilizando SQL Server 2008 o más reciente, creo que la opción 3 debería ser su enfoque preferido. Eso es especialmente cierto si puede cambiar el uso del DATETIMEtipo de datos a un DATEtipo de datos para su posted_date
columna.
Con respecto a la opción 3, se puede encontrar una muy buena explicación sobre algunos problemas aquí: el reparto hasta la fecha es modificable, pero ¿es una buena idea?
No me gustan las opciones 2 y 5 porque los .997
segundos fraccionarios serán solo otro número mágico que la gente querrá "arreglar". Por algunas razones más por las BETWEEN
que no se acepta ampliamente, es posible que desee ver esta publicación .
No me gusta la opción 4 porque convertir los tipos de datos en una cadena para fines de comparación me parece sucio. Una razón más cualitativa para evitarlo en SQL Server es que afecta la capacidad de rastreo, es decir , que no puede realizar una búsqueda de índice y que con frecuencia dará como resultado un rendimiento más deficiente.
Para obtener más información sobre la forma correcta y la manera incorrecta de manejar las consultas de rango de fechas, consulte esta publicación de Aaron Bertrand .
Al partir, podrá mantener su consulta original y se comportará como lo desee si cambia su posted_date
columna de DATETIMEa a DATETIME2(3)
. Eso ahorraría espacio de almacenamiento en el servidor, le daría una mayor precisión con la misma precisión, sería más compatible con los estándares / portátil y le permitiría ajustar fácilmente la precisión / precisión si sus necesidades cambian en el futuro. Sin embargo, esta es solo una opción si está utilizando SQL Server 2008 o posterior.
Como un poco de trivia, la 1/300
segunda precisión DATETIMEparece ser una retención de UNIX según esta respuesta de StackOverflow . Sybase que tiene una herencia compartida tiene un similares 1/300
de un segundo de precisión en sus DATETIME
yTIME
tipos de datos pero sus dígitos menos significativos son un toque diferente a "0", "3" y "6". En mi opinión, la 1/300
precisión de un segundo y / o 3,33 ms es una decisión arquitectónica desafortunada ya que el bloque de 4 bytes por el momento en el DATETIMEtipo de datos de SQL Server podría haber admitido fácilmente la precisión de 1 ms.
datetime3
agregan 70 (vs. 7) dígitos de precisión? La mejor práctica es usar un valor donde la precisión no importa, es decir, <el comienzo del siguiente segundo, minuto, hora o día vs. <= el final del segundo, minuto, hora o día anterior.
Conversión Implícita
Supuse que el tipo de datos post_date es Datetime. Sin embargo, no importa si el tipo en el otro lado es Datetime, Datetime2 o simplemente Time porque la cadena (Varchar) se convertirá implícitamente en Datetime.
Con post_date declarado como Datetime2 (u Time), la posted_date <= '2015-07-27 23:59:59.99999'
cláusula where falla porque aunque 23:59:59.99999
es un valor válido de Datetime2, este no es un valor válido de Datetime:
Conversion failed when converting date and/or time from character string.
Rango de tiempo para fecha y hora
El rango de tiempo de Fecha y hora es de 00:00:00 a 23: 59: 59.997. Por lo tanto, 23: 59: 59.999 está fuera de rango y debe redondearse hacia arriba o hacia abajo al valor más cercano.
Exactitud
Además, los valores de fecha y hora se redondean en incrementos de .000, .003 o .007 segundos. (es decir, 000, 003, 007, 010, 013, 017, 020, ..., 997)
Este no es el caso con el valor 2015-07-27 23:59:59.999
que está dentro de este rango: 2015-07-27 23:59:59.997
y 2015-07-28 0:00:00.000
.
Este rango corresponde a las opciones anteriores y siguientes más cercanas, ambas terminando con .000, .003 o .007.
Redondeando hacia arriba o hacia abajo ?
Debido a que es más cercano a 2015-07-28 0:00:00.000
(1 frente a -2) que 2015-07-27 23:59:59.997
, la cadena se redondea y se convierte este valor de fecha y hora: 2015-07-28 0:00:00.000
.
Con un límite superior como 2015-07-27 23:59:59.998
(o .995, .996, .997, .998), se habría redondeado hacia abajo 2015-07-27 23:59:59.997
y su consulta habría funcionado como se esperaba. Sin embargo, no habría sido una solución sino un valor afortunado.
Datetime2 o tipos de hora
Datetime2 y rangos de tiempo de tiempo son 00:00:00.0000000
a través de 23:59:59.9999999
con una precisión de 100 ns (el último dígito, cuando se usa con una precisión de 7 dígitos).
Sin embargo, un rango de fecha y hora (3) no es similar al rango de fecha y hora:
0:0:00.000
a23:59:59.997
0:0:00.000000000
a23:59:59.999
Solución
Al final, es más seguro buscar fechas por debajo del día siguiente que las fechas por debajo o iguales a lo que crees que es el último fragmento de la hora del día. Esto se debe principalmente a que sabe que el día siguiente siempre comienza a las 0: 00: 00,000 pero que los diferentes tipos de datos pueden no tener la misma hora al final del día:
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
le dará resultados precisos y es la mejor opción<= 2015-07-27 23:59:59.xxx
puede devolver valores inesperados cuando no se redondea a lo que cree que debería ser.Podríamos pensar que cambiar [date_date] a Datetime2 y su mayor precisión podría solucionar este problema, pero no ayudará porque la cadena todavía se convierte a Datetime. Sin embargo, si se agrega un elenco cast(2015-07-27 23:59:59.999' as datetime2)
, esto funciona bien
Moldear y Convertir
Cast puede convertir un valor de hasta 3 dígitos a Datetime o con hasta 9 dígitos a Datetime2 u Time y redondearlo a la precisión correcta.
Cabe señalar que Cast of Datetime2 y Time2 pueden dar resultados diferentes:
select cast('20150101 23:59:59.999999999' as datetime2(7))
es redondeado 2015-05-03 00: 00: 00.0000000 (para un valor superior a 999999949)select cast('23:59:59.999999999' as time(7))
=> 23: 59: 59.9999999En cierto modo, soluciona el problema que la fecha y hora tiene con los incrementos de 0, 3 y 7, aunque siempre es mejor buscar fechas antes del primer nano segundo del día siguiente (siempre 0: 00: 00.000).
Fuente MSDN: fecha y hora (Transact-SQL)
Esta redondeando
select cast('2015-07-27 23:59:59.999' as datetime)
returns 2015-07-28 00:00:00.000
.998, .997, .996, .995 todos emitidos / redondos a .997
Debería usar
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'
Vea la precisión en este enlace
Siempre informado como .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.
¿Qué significa eso?