¿Cuál es una buena manera de comprobar si dos fechas y horas están en el mismo día calendario en TSQL?

StackOverflow https://stackoverflow.com/questions/22570

Pregunta

Este es el problema que tengo:Tengo una consulta grande que necesita comparar fechas y horas en la cláusula donde para ver si dos fechas son el mismo día.Mi solución actual, que apesta, es enviar las fechas y horas a una UDF para convertirlas a la medianoche del mismo día y luego verificar la igualdad de esas fechas.Cuando se trata del plan de consulta, esto es un desastre, como lo son casi todas las UDF en uniones o cláusulas Where.Este es uno de los únicos lugares en mi aplicación donde no he podido eliminar las funciones y darle al optimizador de consultas algo que realmente pueda usar para localizar el mejor índice.

En este caso, volver a fusionar el código de función en la consulta parece poco práctico.

Creo que me falta algo simple aquí.

Aquí está la función como referencia.

if not exists (select * from dbo.sysobjects 
              where id = object_id(N'dbo.f_MakeDate') and               
              type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
  exec('create function dbo.f_MakeDate() returns int as 
         begin declare @retval int return @retval end')
go

alter function dbo.f_MakeDate
(
    @Day datetime, 
    @Hour int, 
    @Minute int
)
returns datetime
as

/*

Creates a datetime using the year-month-day portion of @Day, and the 
@Hour and @Minute provided

*/

begin

declare @retval datetime
set @retval = cast(
    cast(datepart(m, @Day) as varchar(2)) + 
    '/' + 
    cast(datepart(d, @Day) as varchar(2)) + 
    '/' + 
    cast(datepart(yyyy, @Day) as varchar(4)) + 
    ' ' + 
    cast(@Hour as varchar(2)) + 
    ':' + 
    cast(@Minute as varchar(2)) as datetime)
return @retval
end

go

Para complicar las cosas, me uniré a las tablas de zonas horarias para comparar la fecha con la hora local, que podría ser diferente para cada fila:

where 
dbo.f_MakeDate(dateadd(hh, tz.Offset + 
    case when ds.LocalTimeZone is not null 
    then 1 else 0 end, t.TheDateINeedToCheck), 0, 0) = @activityDateMidnight

[Editar]

Estoy incorporando la sugerencia de @Todd:

where datediff(day, dateadd(hh, tz.Offset + 
    case when ds.LocalTimeZone is not null 
    then 1 else 0 end, t.TheDateINeedToCheck), @ActivityDate) = 0

Mi idea errónea sobre cómo funciona dateiff (el mismo día del año en años consecutivos produce 366, no 0 como esperaba) me hizo desperdiciar mucho esfuerzo.

Pero el plan de consulta no cambió.Creo que necesito volver a la mesa de dibujo con todo el asunto.

¿Fue útil?

Solución

Esto es mucho más conciso:

where 
  datediff(day, date1, date2) = 0

Otros consejos

Prácticamente tienes que mantener limpio el lado izquierdo de la cláusula Where.Entonces, normalmente, harías algo como:

WHERE MyDateTime >= @activityDateMidnight 
      AND MyDateTime < (@activityDateMidnight + 1)

(Algunas personas prefieren DATEADD(d, 1, @activityDateMidnight) en su lugar, pero es lo mismo).

Sin embargo, la tabla TimeZone complica un poco el asunto.No está un poco claro en su fragmento, pero parece que t.TheDateInTable está en GMT con un identificador de zona horaria, y luego está agregando el desplazamiento para comparar con @activityDateMidnight, que está en la hora local.Sin embargo, no estoy seguro de qué es ds.LocalTimeZone.

Si ese es el caso, entonces necesitas poner @activityDateMidnight en GMT.

where
year(date1) = year(date2)
and month(date1) = month(date2)
and day(date1) = day(date2)

Asegúrate de leer Solo en una base de datos se puede obtener una mejora del 1000 % cambiando algunas líneas de código para que esté seguro de que el optimizador puede utilizar el índice de manera efectiva al alterar las fechas

Eric Z Barba:

Guardo todas las fechas en GMT.Este es el caso de uso:Algo sucedió a las 11:00 p. m. EST del día 1, que es el 2 GMT.Quiero ver la actividad del día 1 y estoy en EST, por lo que querré ver la actividad de las 11 p.m.Si simplemente comparara las fechas GMT sin procesar, me perdería cosas.Cada fila del informe puede representar una actividad de una zona horaria diferente.

Correcto, pero cuando dices que estás interesado en la actividad del 1 de enero de 2008 EST:

SELECT @activityDateMidnight = '1/1/2008', @activityDateTZ = 'EST'

solo necesitas convertir eso a GMT (estoy ignorando la complicación de consultar el día antes de que EST pase a EDT, o viceversa):

Table: TimeZone
Fields: TimeZone, Offset
Values: EST, -4

--Multiply by -1, since we're converting EST to GMT.
--Offsets are to go from GMT to EST.
SELECT @activityGmtBegin = DATEADD(hh, Offset * -1, @activityDateMidnight)
FROM TimeZone
WHERE TimeZone = @activityDateTZ

que debería darle '1/1/2008 4:00 a.m.'.Luego, puedes buscar en GMT:

SELECT * FROM EventTable
WHERE 
   EventTime >= @activityGmtBegin --1/1/2008 4:00 AM
   AND EventTime < (@activityGmtBegin + 1) --1/2/2008 4:00 AM

El evento en cuestión se almacena con una hora de evento GMT del 2/1/2008 a las 3:00 a.m.Ni siquiera necesita la zona horaria en EventTable (al menos para este propósito).

Dado que EventTime no está en una función, este es un escaneo de índice directo, lo que debería ser bastante eficiente.Haga de EventTime su índice agrupado y volará.;)

Personalmente, haría que la aplicación convirtiera la hora de búsqueda a GMT antes de ejecutar la consulta.

esto eliminará el componente de tiempo de una fecha para usted:

select dateadd(d, datediff(d, 0, current_timestamp), 0)

Aquí tiene muchas opciones para elegir en términos de opciones.Si está utilizando Sybase o SQL Server 2008, puede crear variables de tipo fecha y asignarles sus valores de fecha y hora.El motor de base de datos le ahorra tiempo.Aquí hay una prueba rápida y sucia para ilustrar (el código está en dialecto Sybase):

declare @date1 date
declare @date2 date
set @date1='2008-1-1 10:00'
set @date2='2008-1-1 22:00'
if @date1=@date2
    print 'Equal'
else
    print 'Not equal'

Para SQL 2005 y versiones anteriores, lo que puede hacer es convertir la fecha a un varchar en un formato que no tenga el componente de hora.Por ejemplo, lo siguiente devuelve 2008.08.22

select convert(varchar,'2008-08-22 18:11:14.133',102)

La parte 102 especifica el formato (Los libros en línea pueden enumerar todos los formatos disponibles)

Entonces, lo que puedes hacer es escribir una función que tome una fecha y hora, extraiga el elemento de fecha y descarte la hora.Al igual que:

create function MakeDate (@InputDate datetime) returns datetime as
begin
    return cast(convert(varchar,@InputDate,102) as datetime);
end

Luego podrás utilizar la función para acompañantes.

Select * from Orders where dbo.MakeDate(OrderDate) = dbo.MakeDate(DeliveryDate)

Eric Z Barba:

la fecha de la actividad está destinada a indicar la zona horaria local, pero no una específica

Bien, volvamos a la mesa de dibujo.Prueba esto:

where t.TheDateINeedToCheck BETWEEN (
    dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, @ActivityDate)
    AND
    dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, (@ActivityDate + 1))
)

que traducirá @ActivityDate a la hora local y lo comparará con eso.Esa es su mejor oportunidad para usar un índice, aunque no estoy seguro de que funcione; debería probarlo y verificar el plan de consulta.

La siguiente opción sería una vista indexada, con un TimeINeedToCheck indexado y calculado. en hora local.Luego simplemente regresa a:

where v.TheLocalDateINeedToCheck BETWEEN @ActivityDate AND (@ActivityDate + 1)

que definitivamente usaría el índice, aunque entonces tiene una ligera sobrecarga al INSERTAR y ACTUALIZAR.

Yo usaría la función dayofyear de datepart:


Select *
from mytable
where datepart(dy,date1) = datepart(dy,date2)
and
year(date1) = year(date2) --assuming you want the same year too

Ver la referencia de la fecha aquí.

En cuanto a las zonas horarias, una razón más para almacenar todas las fechas en una única zona horaria (preferiblemente UTC).De todos modos, creo que las respuestas que utilizan dateiff, datepart y las diferentes funciones de fecha integradas son la mejor opción.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top