2008-08-22 246 views
17

這是我遇到的問題:我有一個大型查詢需要比較where子句中的日期時間,以查看兩個日期是否在同一天。我當前的解決方案很糟糕,就是將日期時間發送到UDF中,將它們轉換爲當天的午夜,然後檢查這些日期是否相等。當涉及到查詢計劃時,這是一場災難,幾乎所有UDF都在聯接或where子句中。這是我的應用程序中唯一的地方之一,我沒有能夠根除這些函數併爲查詢優化器提供實際可用於查找最佳索引的內容。在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 

[編輯]

我納入@託德的建議:

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

我對於如何運作的誤解(同一天連續幾年的年收益率爲366,而不是我所期望的),這使我浪費了很多精力。

但是查詢計劃沒有改變。我認爲我需要回到整個繪圖板。

+0

請參閱馬克布拉克特的答案,這是**右**。我意識到你的問題是2歲,但請讓我們不要讓人們走錯了訪問這個問題的錯誤路徑。 Todd Tingen的回答很有效,但是表現糟糕,就像你在當時發現的那樣! – ErikE 2010-09-12 22:47:50

+0

Mark的答案對於這種情況是正確的,因爲優化器依賴於它。我使用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(d,1,@activityDateMidnight),而不是 - 但它同樣的事情)。

雖然TimeZone表有點複雜。你的代碼片段有點不清楚,但它看起來像t.DateInTable格林威治標準時間格林威治標準時間帶有時區標識符,然後你添加偏移量與@activityDateMidnight進行比較 - 這是在當地時間。不過,我不確定ds.LocalTimeZone是什麼。

如果是這種情況,那麼您需要將@activityDateMidnight改爲GMT。

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 @[email protected] 
    print 'Equal' 
else 
    print 'Not equal' 

對於SQL 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功能:


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

請參見日期部分參考here

0

關於時區,還有一個理由將所有日期存儲在單個時區(最好是UTC)。無論如何,我認爲使用datediff,datepart和不同的內置日期函數的答案是最好的選擇。

2

埃裏克ž鬍子:

我做存儲在格林尼治標準時間的所有日期。這是用例:1號東部時間美國東部時間下午11:00,即格林威治標準時間第二天發生了一件事。我希望看到第一場比賽的活動,而我在東部時間,所以我想看看11PM的活動。如果我只比較原始的格林尼治標準時間日期,我會錯過的東西。報告中的每一行都可以表示來自不同時區的活動。

權,但是當你說你有興趣在2008年1月1日EST活動:

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 

這應該給你 '1/1/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 

有問題的事件存儲與2008年1月2日上午3:00的GMT EVENTTIME。你甚至不需要EventTable中的TimeZone(至少爲此目的)。

由於EventTime不在函數中,因此這是一種直接索引掃描 - 應該非常高效。使EventTime成爲聚集索引,並且它會飛。 ;)

就個人而言,我會讓應用程序在運行查詢之前將搜索時間轉換爲GMT。

1

埃裏克ž鬍子:

活動日期意思是指本地時區,而不是特定的一個

好了 - 回到繪圖板。試試這個:

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。

相關問題