2008-08-22 13 views
17

Aquí está el problema que estoy teniendo: Tengo una consulta de gran tamaño que se necesita comparar datetimes en la cláusula where para ver si dos fechas están en el mismo día. Mi solución actual, que aspira, es el envío de los datetimes en una UDF para convertirlos a la medianoche del mismo día, y después comprobar esas fechas para la igualdad. Cuando se trata el plan de consulta, esto es un desastre, al igual que casi todas las UDF en las combinaciones o donde cláusulas. Este es uno de los únicos lugares en mi solicitud que no he sido capaz de erradicar a las funciones y dar al optimizador de consultas de algo que en realidad se puede utilizar para localizar el mejor índice.¿Qué es una buena manera de comprobar si dos datetimes están en el mismo día calendario en TSQL?

En este caso, no es práctico volver a combinar el código de función en la consulta.

Creo que me falta algo simple aquí.

Aquí está la función de 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 uno a las mesas de zona horaria para comprobar la fecha en contra de la hora local, que puede 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]

I 'm @ incorporar 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 acerca de cómo funciona DateDiff (el mismo día del año en años consecutivos rinde 366, no 0 como esperaba) me hizo perder mucho esfuerzo.

Pero el plan de consulta no cambió. Creo que necesito volver al tablero de dibujo con todo el asunto.

+0

Consulte la respuesta de Mark Brackett, que es la ** correcta **. Me doy cuenta de que su pregunta tiene 2 años, pero por favor no guiemos a la gente por el camino equivocado que visita esta pregunta. La respuesta de Todd Tingen funciona, pero es un rendimiento terrible, como descubriste en ese momento. – ErikE

+0

La respuesta de Mark es correcta para esta situación porque el optimizador depende de ello. Usar dateadd como lo hice es una forma simple y concisa de ver si dos fechas están en el mismo día calendario que fue la pregunta original. – Todd

Respuesta

51

Esto es mucho más concisa:

where 
    datediff(day, date1, date2) = 0 
+6

¿Por qué esto tiene 9 votos y se le otorgó la respuesta? Es conciso, está bien, y condujo al OP por el camino equivocado. Como la segunda fecha, @ActivityDate, es fija, podemos mover las matemáticas al lado derecho y obtener un rendimiento mucho mejor. – ErikE

+2

De acuerdo con @emtucifor. Esta lógica no es sargable por el servidor sql. – DForck42

+0

@ErikE, no entiendo exactamente lo que quiere decir, ¿tiene un fragmento de código? – Joyce

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

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

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

que bastante tienen para mantener el lado izquierdo de su donde cláusula limpia. Así que, normalmente, usted haría algo como:

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

(Algunas personas prefieren DATEADD (d, 1, @activityDateMidnight) en vez - pero es la misma cosa).

La tabla de TimeZone complica un poco la cuestión. Es un poco claro en el fragmento, pero parece que está en t.TheDateInTable GMT con un identificador de zona horaria, y que está a continuación, añadir el desplazamiento con el que comparar @activityDateMidnight - que está en hora local. No estoy seguro de lo que ds.LocalTimeZone es, sin embargo.

Si ese es el caso, entonces necesita obtener @activityDateMidnight en GMT en su lugar.

0

Usted está mucho donde elegir en términos de opciones aquí. 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 la base de datos se deshace del tiempo para usted.Aquí está una prueba rápida y sucia para ilustrar (Código está en Sybase dialecto):

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

Para SQL 2005 y principios de lo que puede hacer es convertir la fecha a un varchar en un formato que no tiene el componente de tiempo. Por ejemplo, los siguientes rendimientos 2008.08.22

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

La parte 102 especifica los libros de formato (en línea puede enumerar para usted todos los formatos disponibles)

Por lo tanto, lo que puede hacer es escribir una función que toma una datetime y extrae el elemento de fecha y descarta la hora. De este modo:

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

A continuación, puede utilizar la función de compañeros de

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

me gustaría utilizar la función de dayofyear datepart:


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

Véase la referencia datepart here.

0

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

2

Eric Barba Z:

almaceno todas las fechas en GMT. Aquí está el caso de uso: algo sucedió a las 11:00 PM EST del 1er, que es el 2º GMT. Quiero ver actividad para el 1er, y estoy en EST así que querré ver la actividad de las 11PM. Si solo comparase las fechas de GMT crudas, extrañaría las cosas. Cada fila en el informe puede representar una actividad de un huso horario diferente.

derecho, pero cuando usted dice que está interesado en la actividad para el 1 Ene 2008 est:

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

sólo tiene que convertir que a GMT (estoy haciendo caso omiso de la complicación de la consulta para el día antes EST va 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 se debe dar '1/1/2008 04 a.m.'. A continuación, puede simplemente 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 un GMT EventTime de 1/2/2008 3:00 am. Ni siquiera necesita el TimeZone en EventTable (para este propósito, al menos).

Dado que EventTime no tiene una función, esta es una exploración de índice directa, que debería ser bastante eficiente. Haga de EventTime su índice agrupado, y volará. ;)

Personalmente, la aplicación convertiría el tiempo de búsqueda en GMT antes de ejecutar la consulta.

1

Eric Barba Z:

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

bien - de nuevo a la mesa de dibujo. Pruebe 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 compare con eso. Esa es tu mejor oportunidad para usar un índice, aunque no estoy seguro de que funcione, deberías probarlo y verificar el plan de consulta.

La siguiente opción sería una vista indizada, con un TimeINeedToCheck calculado e indexado en la hora local. A continuación, sólo tiene que ir de nuevo a:

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

que sin duda utilizar el índice - a pesar de que tiene una ligera sobrecarga en insertar y actualizar a continuación.

Cuestiones relacionadas