2008-08-22 48 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。

相关问题