Pergunta

Aqui está o problema que estou tendo:Eu tenho uma consulta grande que precisa comparar datas e horas na cláusula where para ver se duas datas estão no mesmo dia.Minha solução atual, que é uma droga, é enviar as datas e horas para uma UDF para convertê-las para meia-noite do mesmo dia e, em seguida, verificar a igualdade dessas datas.Quando se trata do plano de consulta, isso é um desastre, assim como quase todas as UDFs em junções ou cláusulas where.Este é um dos únicos lugares em meu aplicativo onde não consegui erradicar as funções e fornecer ao otimizador de consulta algo que ele possa realmente usar para localizar o melhor índice.

Nesse caso, mesclar o código da função de volta à consulta parece impraticável.

Acho que estou faltando algo simples aqui.

Aqui está a função para referência.

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, estou ingressando nas tabelas de fuso horário para verificar a data em relação ao horário local, que pode ser diferente para cada linha:

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]

Estou incorporando a sugestão 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

Meu equívoco sobre como o datediff funciona (o mesmo dia do ano em anos consecutivos rende 366, e não 0 como eu esperava) me fez desperdiçar muito esforço.

Mas o plano de consulta não mudou.Acho que preciso voltar à prancheta com tudo isso.

Foi útil?

Solução

Isso é muito mais conciso:

where 
  datediff(day, date1, date2) = 0

Outras dicas

Você praticamente precisa manter o lado esquerdo da cláusula where limpo.Então, normalmente, você faria algo como:

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

(Algumas pessoas preferem DATEADD(d, 1, @activityDateMidnight) - mas é a mesma coisa).

A tabela TimeZone complica um pouco a questão.Não está claro no seu snippet, mas parece que t.TheDateInTable está em GMT com um identificador de fuso horário e que você está adicionando o deslocamento para comparar com @activityDateMidnight - que está no horário local.Não tenho certeza do que é ds.LocalTimeZone.

Se for esse o caso, você precisará colocar @activityDateMidnight no GMT.

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

Certifique-se de ler Somente em um banco de dados você pode obter 1000% de melhoria alterando algumas linhas de código para que você tenha certeza de que o otimizador pode utilizar o índice de forma eficaz ao mexer com datas

Eric Z Barba:

Eu armazeno todas as datas em GMT.Aqui está o caso de uso:algo aconteceu às 23h EST do dia 1º, que é o 2º GMT.Quero ver a atividade do dia 1º e estou em EST, então vou querer ver a atividade das 23h.Se eu apenas comparasse os horários GMT brutos, sentiria falta de algumas coisas.Cada linha do relatório pode representar uma atividade de um fuso horário diferente.

Certo, mas quando você diz que está interessado em atividades para 1º de janeiro de 2008 EST:

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

você só precisa converter que para GMT (estou ignorando a complicação de consultar o dia anterior ao EST ir para EDT ou vice-versa):

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 deve fornecer '01/01/2008 4h00'.Depois, basta pesquisar em GMT:

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

O evento em questão é armazenado com um EventTime GMT de 02/01/2008 às 3h.Você nem precisa do TimeZone na EventTable (pelo menos para esse fim).

Como EventTime não está em uma função, esta é uma varredura direta de índice - o que deve ser bastante eficiente.Faça do EventTime seu índice clusterizado e ele voará.;)

Pessoalmente, gostaria que o aplicativo convertesse o horário de pesquisa em GMT antes de executar a consulta.

isso removerá o componente de tempo de uma data para você:

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

Você não terá mais opções em termos de opções aqui.Se você estiver usando Sybase ou SQL Server 2008, poderá criar variáveis ​​do tipo data e atribuir a elas seus valores de data e hora.O mecanismo de banco de dados economiza tempo para você.Aqui está um teste rápido e sujo para ilustrar (o código está no dialeto 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 e anteriores, o que você pode fazer é converter a data em um varchar em um formato que não possui o componente de hora.Por exemplo, o seguinte retorna 2008.08.22

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

A parte 102 especifica a formatação (os livros online podem listar para você todos os formatos disponíveis)

Então, o que você pode fazer é escrever uma função que pegue uma data e hora e extraia o elemento de data e descarte a hora.Igual a:

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

Você pode então usar a função para companheiros

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

Eric Z Barba:

a data da atividade destina-se a indicar o fuso horário local, mas não um fuso horário específico

Ok - de volta à prancheta.Experimente isto:

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 traduzirá @ActivityDate para a hora local e comparará com isso.Essa é sua melhor chance de usar um índice, embora não tenha certeza se funcionará - você deve tentar e verificar o plano de consulta.

A próxima opção seria uma visualização indexada, com um TimeINeedToCheck indexado e computado na hora local.Depois é só voltar para:

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

que definitivamente usaria o índice - embora você tenha uma ligeira sobrecarga em INSERT e UPDATE.

Eu usaria a função 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

Veja a referência do datepart aqui.

Em relação aos fusos horários, mais um motivo para armazenar todas as datas em um único fuso horário (de preferência UTC).De qualquer forma, acho que as respostas usando datediff, datepart e as diferentes funções de data integradas são sua melhor aposta.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top