Que tal amigos, hoy les mostraré lo que algunos consideran
un BUG al momento de filtrar datos por una columna tipo DATETIME.
Primero, creamos
una tabla de prueba con algunos datos, les dejo un LINK con el script o el script directamente para copiar.
IF OBJECT_ID (N'pruebas', N'U') IS NOT NULL DROP TABLE pruebas CREATE TABLE pruebas( id INT IDENTITY( 1,1 ), fecha DATETIME ) INSERT INTO pruebas( fecha ) VALUES ('2012-12-01 00:04:40'), ('2012-12-01 05:35:29'), ('2012-12-01 08:54:10'),('2012-12-01 09:04:34'), ('2012-12-01 09:37:48'), ('2012-12-01 09:39:55'), ('2012-12-01 10:28:14'),('2012-12-01 11:46:13'), ('2012-12-01 12:01:40'), ('2012-12-01 13:22:10'), ('2012-12-01 13:27:37'),('2012-12-01 13:35:01'), ('2012-12-01 14:40:25'), ('2012-12-01 15:01:15'), ('2012-12-01 15:36:44'),('2012-12-01 16:39:27'), ('2012-12-01 17:21:07'), ('2012-12-01 17:46:49'), ('2012-12-01 17:54:16'),('2012-12-01 18:10:10'), ('2012-12-01 19:58:17'), ('2012-12-01 20:05:47'), ('2012-12-01 20:31:41'),('2012-12-01 22:19:10'), ('2012-12-02 09:02:21'), ('2012-12-02 10:45:44'), ('2012-12-02 11:58:16'),('2012-12-02 14:21:06'), ('2012-12-02 17:07:40'), ('2012-12-02 17:44:29'), ('2012-12-02 20:50:20'),('2012-12-03 07:47:26'), ('2012-12-03 08:06:27'), ('2012-12-03 08:29:54'), ('2012-12-03 08:42:01'), ('2012-12-03 09:24:39'), ('2012-12-03 09:26:37'), ('2012-12-03 09:30:15'), ('2012-12-03 09:48:14'), ('2012-12-03 09:55:21'), ('2012-12-03 10:16:13'), ('2012-12-03 10:48:05'), ('2012-12-03 10:55:11'), ('2012-12-03 10:55:23'), ('2012-12-03 10:56:33'), ('2012-12-03 10:57:39'), ('2012-12-03 11:02:07'), ('2012-12-03 11:08:28'), ('2012-12-03 11:21:47'), ('2012-12-03 11:22:33'), ('2012-12-03 11:26:58'), ('2012-12-03 11:36:33'), ('2012-12-03 11:52:40'), ('2012-12-03 12:12:28'), ('2012-12-03 12:20:56'), ('2012-12-03 12:28:10'), ('2012-12-03 12:42:23'), ('2012-12-03 12:52:23'), ('2012-12-03 13:11:18'), ('2012-12-03 13:23:37'), ('2012-12-03 13:23:46'), ('2012-12-03 13:32:52'), ('2012-12-03 13:33:00'), ('2012-12-03 13:49:14'), ('2012-12-03 14:30:19'), ('2012-12-03 14:46:38'), ('2012-12-03 15:14:41'), ('2012-12-03 15:31:47'), ('2012-12-03 16:03:05'), ('2012-12-03 17:19:39'), ('2012-12-03 17:41:06'), ('2012-12-03 17:54:31'), ('2012-12-03 18:33:24'), ('2012-12-03 21:32:30'), ('2012-12-04 08:22:47'), ('2012-12-04 08:27:28'), ('2012-12-04 08:43:58'), ('2012-12-04 08:59:31'), ('2012-12-04 09:22:33'), ('2012-12-04 09:28:17'), ('2012-12-04 09:35:36'), ('2012-12-04 10:04:03'), ('2012-12-04 10:53:45'), ('2012-12-04 11:07:15'), ('2012-12-04 11:12:53'), ('2012-12-04 11:26:23'), ('2012-12-04 11:30:38'), ('2012-12-04 11:42:46'), ('2012-12-04 12:32:57'), ('2012-12-04 12:40:16'), ('2012-12-04 12:59:17'), ('2012-12-04 13:00:09'), ('2012-12-04 13:08:13'), ('2012-12-04 13:09:56'), ('2012-12-04 13:23:39'), ('2012-12-04 13:26:42'), ('2012-12-04 13:51:10'), ('2012-12-04 14:57:20'), ('2012-12-04 15:20:42'), ('2012-12-04 15:37:18'), ('2012-12-04 16:14:04'), ('2012-12-04 16:16:06'), ('2012-12-04 17:21:32'), ('2012-12-04 17:28:54'), ('2012-12-04 17:42:00'), ('2012-12-04 18:04:24'), ('2012-12-04 18:05:48'), ('2012-12-04 19:13:49'), ('2012-12-04 19:23:38'), ('2012-12-04 21:27:58'), ('2012-12-04 22:42:20'), ('2012-12-05 08:48:58'), ('2012-12-05 09:00:59'), ('2012-12-05 09:24:29'), ('2012-12-05 10:09:48'), ('2012-12-05 10:35:41'), ('2012-12-05 10:39:00'), ('2012-12-05 11:21:44'), ('2012-12-05 11:35:34'), ('2012-12-05 12:28:42'), ('2012-12-05 12:31:30'), ('2012-12-05 12:54:57'), ('2012-12-05 13:30:31'), ('2012-12-05 13:39:06'), ('2012-12-05 13:43:25'), ('2012-12-05 13:47:54'), ('2012-12-05 13:59:07'), ('2012-12-05 14:19:53'), ('2012-12-05 14:34:23'), ('2012-12-05 14:42:55'), ('2012-12-05 15:13:39'), ('2012-12-05 15:52:14'), ('2012-12-05 15:58:17'), ('2012-12-05 16:13:00'), ('2012-12-05 16:17:26'), ('2012-12-05 18:02:29'), ('2012-12-05 19:21:14'), ('2012-12-05 19:21:53'), ('2012-12-05 21:13:34'), ('2012-12-05 22:16:08')
En total
son 140 filas, agrupando por día, tenemos como resultado lo siguiente:
SELECT CAST( fecha AS DATE ) as dia, COUNT( id ) as total FROM pruebas GROUP BY CAST( fecha AS DATE )
Como podemos observar, para el día
1 y 2 de Diciembre tenemos 24 y 7 filas( 31 en total ), ahora ejecutemos la
siguiente consulta:
SELECT * FROM pruebas WHERE fecha BETWEEN '20121201' AND '20121202'
Como podemos observar solo arroja
24 filas, y que pasó con las 7 filas restantes??? Si observamos detenidamente la
consulta:
SELECT * FROM pruebas WHERE fecha BETWEEN '20121201' AND '20121202'
La columna fecha es de tipo DATETIME, pero que tipo de dato son los
filtros que estamos aplicando? Ejecutemos la siguiente consulta:
SELECT CAST( '20121201' AS DATETIME ) as fecha1 , CAST( '20121202' AS DATETIME ) as fecha2
Sorpresa? No, porque está es la
forma en que el motor maneja las fechas, observemos y ejecutemos la siguiente
consulta:
DECLARE @hoy AS DATETIME SET @hoy = GETDATE() SELECT @hoy AS fechaDATETIME , CAST( @hoy AS DATE ) AS fechaDATE , CAST( CAST( @hoy AS DATE ) AS DATETIME ) AS fecha
Mostramos la fecha hora actual que es de tipo DATETIME, lo
convertimos a DATE y nuevamente a DATETIME, como podemos observar la hora
actual no es la misma.
Perfecto, ya quedó comprendido,
debo utilizar un tipo de dato DATETIME para
hacer el filtro de la siguiente manera:
SELECT * FROM pruebas WHERE fecha BETWEEN '20121201 00:00' AND '20121202 23:59'
Claro, quedó solucionado, ya
podemos filtrar por fechas, pero... que pasa si agregamos una fila con la hora
23:59:59
INSERT INTO pruebas VALUES( '2012-12-02 23:59:59' )
Y ejecutamos nuevamente la
consulta de agrupación para observar cuantas filas tenemos por día.
SELECT CAST( fecha AS DATE ) as dia, COUNT( id ) as total FROM pruebas GROUP BY CAST( fecha AS DATE )
Observemos que ya tenemos 8 filas en el día 2 y apliquemos
el query para extraer las filas del día 1 y 2 nuevamente:
SELECT * FROM pruebas WHERE fecha BETWEEN '20121201 00:00' AND '20121202 23:59'
Y ahora que paso? si ya aplicamos
la consulta con los filtros fecha-hora... Ok, esto es cierto, pero recordemos
que el tipo DATETIME en SQL SERVER almacena hasta milésimas de segundo, por lo
tanto en la consulta debemos especificar hasta la ultima milésima de segundo
del día 2, en este caso solo manejamos hasta segundos:
SELECT fecha FROM pruebas WHERE fecha BETWEEN '20121201 00:00' AND '20121202 23:59:59'
Ahora, hagamos la prueba
agregando una fila con milésimas de segundo:
INSERT INTO pruebas VALUES( '2012-12-02 23:59:59.997' )
Ejecutamos la consulta de
agrupación:
SELECT CAST( fecha AS DATE ) as dia, COUNT( id ) as total FROM pruebas GROUP BY CAST( fecha AS DATE )
Y ahora ya tenemos 9 filas en el
día 2 y para poder obtener esas filas es necesario especificar el ultimo
milisegundo del día 2 de la siguiente manera:
SELECT fecha FROM pruebas WHERE fecha BETWEEN '20121201 00:00' AND '20121202 23:59:59.999'
Ok, comprendido, pero también
esto lo consigo entonces filtrando de la siguiente manera???:
SELECT fecha FROM pruebas WHERE fecha BETWEEN '20121201' AND '20121203'
Ya que 2012-12-03 es igual a 2012-12-03
00:00:00.000 porque SQL SERVER así lo interpreta cuando se comparan DATE y un DATETIME.
ERROR, esto funcionará siempre y cuando no tengamos una fila con
fecha 2012-12-03 00:00:00.000
INSERT INTO pruebas VALUES( '2012-12-03 00:00:00.000' )
Si ejecutamos el mismo query,
obtenemos lo siguiente:
Esta bien, ya comprendí, entonces
esto lo puedo lograr convirtiendo la columna a tipo DATE y filtrando solo por
el día sin horas, minutos y segundos??
Respuesta: SI, pero no es recomendable.
Al hacer esto afectaremos la búsqueda del query, supongamos que nuestra
tabla está indexada por este campo:
CREATE NONCLUSTERED INDEX IXfecha ON pruebas (fecha);
Después activamos la Inclusión
del Plan de Ejecución en el resultado de la consulta, a través de CTRL + M o en el botón que describe la siguiente
imagen:
Y ejecutamos la consulta:
SELECT fecha FROM pruebas WHERE fecha BETWEEN '20121201 00:00' AND '20121202 23:59:59.999'
Como podemos observar, hay una
pestaña más en nuestro resultado que es el Plan de Ejecución y además vemos una
gráfica del proceso que hizo el motor para obtener el resultado; Al ver un Index Seek no nos debemos preocupar,
esto quiere decir que el motor está buscando o filtrando por un Índice, lo
cual es correcto. Pero que pasa si convertimos la columna en tipo DATE para
poder lograr lo que unos pasos antes comentabamos:
SELECT fecha FROM pruebas WHERE CAST( fecha AS DATE) BETWEEN '20121201' AND '20121202'
Si, también obtendremos las
mismas 33 filas pero a que costo, observemos el plan de ejecución de la
consulta nuevamente:
Hizo más pasos para obtener las
mismas filas, entonces esto quiere decir que nuestra consulta no está del
todo bien, más adelante les explicaré que significa tantas imágenes en el plan
de ejecución.
Esto sucede porque al aplicar una
función sobre una columna que se encuentra indexada, esta pierde su propiedad y
estaremos buscando sobre una columna NO INDEXADA.
Ok ok, ya comprendí, pero
entonces que puedo hacer para obtener esos 33 registros sin afectar el
rendimiento del query???
Respuesta: Volver a los operadores
mayor o igual que( >= ) y menor ( < ) de la siguiente manera:
SELECT fecha FROM pruebas WHERE fecha >= '20121201' AND fecha < '20121203'
Observemos
el Plan de Ejecución ( CTRL + M )
De esta
forma estaremos tomando las filas que tengan 2012-12-01 00:00:00.000 y además
aquellas que sean menores a 2012-12-03 00:00:00.000 como 2012-12-02 23:59:59.999.
En resumen,
debemos de conocer cómo funciona el tipo de dato DATETIME para poder aplicar un
BETWEEN, en caso contrario lo mejor siempre será utilizar los operadores
mayor o igual que( >= ) y menor ( < ).
SALUDOS!
COMPARTE ESTA INFORMACION SI TE PARECIO INTERESANTE
0 comentarios:
Publicar un comentario