Каков хороший способ проверить, совпадают ли две даты и времени в один и тот же календарный день в TSQL?

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

Вопрос

Вот проблема, с которой я столкнулся:У меня есть большой запрос, в котором необходимо сравнить дату и время в предложенииwhere, чтобы узнать, совпадают ли две даты в один и тот же день.Мое текущее решение, которое отстой, состоит в том, чтобы отправить даты и время в UDF, чтобы преобразовать их в полночь того же дня, а затем проверить эти даты на равенство.Когда дело доходит до плана запроса, это катастрофа, как и почти все UDF в соединениях или предложенияхwhere.Это одно из немногих мест в моем приложении, где мне не удалось искоренить функции и дать оптимизатору запросов что-то, что он действительно может использовать для поиска лучшего индекса.

В этом случае объединение кода функции обратно в запрос кажется нецелесообразным.

Я думаю, что мне здесь не хватает чего-то простого.

Вот функция для справки.

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

Чтобы усложнить ситуацию, я присоединяюсь к таблицам часовых поясов, чтобы сверить дату с местным временем, которое может быть разным для каждой строки:

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

[Редактировать]

Я включаю предложение @Todd:

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

Мое неправильное представление о том, как работает dateiff (один и тот же день года в последующие годы дает 366, а не 0, как я ожидал) заставило меня потратить много усилий.

Но план запроса не изменился.Я думаю, мне нужно вернуться к чертежной доске со всем этим.

Это было полезно?

Решение

Это гораздо более кратко:

where 
  datediff(day, date1, date2) = 0

Другие советы

Вам в значительной степени нужно поддерживать чистоту левой части предложенияwhere.Итак, обычно вы делаете что-то вроде:

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

(Некоторые люди предпочитают вместо этого DATEADD(d, 1, @activityDateMidnight) - но это то же самое).

Однако таблица TimeZone немного усложняет ситуацию.Из вашего фрагмента это немного неясно, но похоже, что t.TheDateInTable находится в GMT с идентификатором часового пояса, и что вы затем добавляете смещение для сравнения с @activityDateMidnight, которое соответствует местному времени.Однако я не уверен, что такое ds.LocalTimeZone.

В этом случае вам нужно вместо этого перевести @activityDateMidnight в GMT.

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

Обязательно прочитайте Только в базе данных можно получить улучшение на 1000%+, изменив несколько строк кода чтобы вы были уверены, что оптимизатор сможет эффективно использовать индекс при работе с датами.

Эрик З. Бирд:

Я храню все даты в формате GMT.Вот вариант использования:что-то произошло в 23:00 по восточному стандартному времени 1-го числа, то есть 2-го числа по Гринвичу.Я хочу увидеть активность 1-го числа, и я нахожусь в восточном стандартном времени, поэтому мне нужно увидеть активность в 23:00.Если бы я просто сравнил необработанные даты и время по Гринвичу, я бы что-то упустил.Каждая строка в отчете может представлять действие из другого часового пояса.

Верно, но когда вы говорите, что вас интересуют мероприятия на 1 января 2008 г. по восточному стандартному времени:

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

вам просто нужно конвертировать что на GMT (я игнорирую сложность запроса за день до того, как EST перейдет в EDT, или наоборот):

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

который должен дать вам «01.01.2008 4:00 утра».Затем вы можете просто выполнить поиск по GMT:

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

Событие, о котором идет речь, хранится с GMT EventTime, равным 02.01.2008 3:00.Вам даже не нужен TimeZone в EventTable (по крайней мере, для этой цели).

Поскольку EventTime не входит в функцию, это прямое сканирование индекса, которое должно быть довольно эффективным.Сделайте EventTime своим кластерным индексом, и все будет работать.;)

Лично я бы попросил приложение преобразовать время поиска в GMT перед запуском запроса.

это удалит компонент времени из даты для вас:

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

Здесь вы избалованы выбором вариантов.Если вы используете Sybase или SQL Server 2008, вы можете создавать переменные типа date и присваивать им значения даты и времени.Ядро базы данных избавит вас от этого времени.Вот быстрый и грубый тест для иллюстрации (код написан на диалекте 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'

Для SQL 2005 и более ранних версий вы можете преобразовать дату в varchar в формате, не имеющем компонента времени.Например, следующее возвращает 2008.08.22.

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

Часть 102 определяет форматирование (в онлайн-книгах можно перечислить все доступные форматы).

Итак, вы можете написать функцию, которая принимает дату и время, извлекает элемент даты и отбрасывает время.Вот так:

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

Затем вы можете использовать функцию для компаньонов

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

Эрик З. Бирд:

дата активности предназначена для указания местного часового пояса, а не конкретного

Хорошо, вернемся к чертежной доске.Попробуй это:

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))
)

который переведет @ActivityDate на местное время и сравнит его с этим.Это ваш лучший шанс использовать индекс, хотя я не уверен, что он сработает — вам следует попробовать и проверить план запроса.

Следующим вариантом будет индексированное представление с индексированным вычисленным TimeINeedToCheck. по местному времени.Затем вы просто возвращаетесь к:

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

который определенно будет использовать индекс - хотя тогда у вас будут небольшие накладные расходы на INSERT и UPDATE.

Я бы использовал функцию dayofyear для datepart:


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

См. ссылку на дату. здесь.

Что касается часовых поясов, есть еще одна причина хранить все даты в одном часовом поясе (предпочтительно UTC).В любом случае, я думаю, что лучше всего вам подойдут ответы с использованием dateiff, datepart и различных встроенных функций даты.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top