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

Вот проблема, с которой я сталкиваюсь: у меня есть большой запрос, который должен сравнить дату и время в предложении 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

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

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


person Eric Z Beard    schedule 22.08.2008    source источник
comment
См. ответ Марка Брэкетта, который является правильным. Я понимаю, что вашему вопросу уже 2 года, но, пожалуйста, давайте не будем вести людей по неправильному пути, которые посещают этот вопрос. Ответ Тодда Тингена работает, но это ужасная производительность, как вы обнаружили в то время!   -  person ErikE    schedule 13.09.2010
comment
Ответ Марка правильный для этой ситуации, потому что от него зависел оптимизатор. Использование dateadd так, как это сделал я, — это простой и лаконичный способ узнать, совпадают ли две даты с одним и тем же календарным днем, что и было исходным вопросом.   -  person Todd    schedule 15.12.2010


Ответы (9)


Это намного короче:

where 
  datediff(day, date1, date2) = 0
person Community    schedule 22.08.2008
comment
Почему это имеет 9 голосов и получил ответ? Это кратко, хорошо, и привело ОП по неправильному пути. Поскольку вторая дата, @ActivityDate, фиксирована, мы можем переместить математику в правую сторону и получить гораздо лучшую производительность. - person ErikE; 13.09.2010
comment
Согласен с @emtucifor. Эта логика не поддерживается сервером sql. - person DForck42; 13.09.2010
comment
@ErikE, я не совсем понимаю, что ты имеешь в виду, у тебя есть фрагмент кода? - person Joyce; 25.08.2017
comment
@Joyce, посмотрите на ответ Марка Брэкетта. Он поставил MyDateTime с одной стороны любого математического оператора, отдельно. Это означает, что механизм запросов может выполнять поиск по диапазону и переходить непосредственно в B-дереве к строкам, которые находятся между двумя временами. С этим ужасным, ужасным, ужасным советом здесь каждая строка в таблице должна быть обработана математикой, что представляет собой сканирование и ужасно для производительности. Если в таблице 100 миллионов строк, и только 10 строк соответствуют критерию, подумайте, как плохо просматривать их все, чтобы найти 10!!!!!! - person ErikE; 25.08.2017

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

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

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

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

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

person Mark Brackett    schedule 22.08.2008

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

person SQLMenace    schedule 22.08.2008

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

select dateadd(d, datediff(d, 0, current_timestamp), 0)
person AlexCuse    schedule 22.08.2008

Эрик Z Бирд:

Я храню все даты в GMT. Вот пример использования: что-то произошло в 23:00 по восточному поясному времени 1-го числа, то есть 2-го по Гринвичу. Я хочу увидеть активность на 1-е число, и я нахожусь в EST, поэтому я хочу увидеть активность в 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». Затем вы можете просто выполнить поиск по Гринвичу:

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

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

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

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

person Mark Brackett    schedule 22.08.2008
comment
если вы вместо этого используете оператор BETWEEN, это окажет негативное влияние? - person Felipe Rodriguez; 01.06.2021
comment
@FelipeRodriguez - не совсем так; просто обратите внимание, что BETWEEN включает оба конца, поэтому диапазоны могут перекрываться. - person Mark Brackett; 16.06.2021

Вы избалованы выбором с точки зрения вариантов здесь. Если вы используете 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)
person Rad    schedule 22.08.2008

Эрик Z Бирд:

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

Ладно - вернемся к чертежной доске. Попробуй это:

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.

person Mark Brackett    schedule 23.08.2008

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


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

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

person brendan    schedule 22.08.2008

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

person Tundey    schedule 22.08.2008