2008-08-22 3 views
17

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

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

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

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

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 

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

I в том числе @ Предложение Тодда:

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

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

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

+0

См. Ответ Марка Брэкетта, который является ** правым ** одним. Я понимаю, что ваш вопрос - 2 года, но, пожалуйста, давайте не будем приводить людей к неправильному пути, которые посещают этот вопрос. Ответ Тодда Тингена работает, но это ужасное исполнение, как вы нашли в то время! – ErikE 2010-09-12 22:47:50

+0

Ответ от Марка правилен для этой ситуации, потому что оптимизатор зависит от нее. Используя dateadd, как я это сделал, это простой и сжатый способ проверить, соответствуют ли две даты одному календарному дню, который был исходным вопросом. – Todd 2010-12-15 03:30:24

ответ

51

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

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

это будет удалить компонент времени с датой для вас:

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

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

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

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

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

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

0

Вы испорчены для выбора с точки зрения вариантов здесь. Если вы используете Sybase или SQL Server 2008, вы можете создавать переменные типа date и присваивать им свои значения datetime. Механизм базы данных избавляет вас от времени.Вот быстрый и грязный тест, чтобы показать (код в Sybase диалекта):

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' 

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

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

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

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

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

Я хотел бы использовать функцию DayOfYear из DATEPART:


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

Смотрите ссылку DATEPART here.

0

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

2

Eric Z Beard:

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

Да, но когда вы говорите, что вы заинтересованы в деятельности на 1 января 2008 EST:

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

вам просто нужно конвертировать что пояс (я не обращая внимание на усложнение запросов за день до 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 

, который должен дать вам '1/1/2008 4:00 AM'. Тогда вы можете просто поиск по Гринвичу:

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

событие в вопросе сохраняется с GMT EventTime от 1/2/2008 3:00 AM. Вам даже не требуется TimeZone в EventTable (для этой цели, по крайней мере).

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

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

1

Eric Z Beard:

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

Хорошо - назад к чертежной доске. Попробуйте следующее:

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 тогда.

Смежные вопросы