有什么方法可以检查两个日期时间是否在TSQL中的同一个日历日?

时间:2008-08-22 14:53:48

标签: sql sql-server tsql datetime user-defined-functions

以下是我遇到的问题:我有一个大型查询需要比较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

我对约瑟夫如何工作的误解(连续几年的同一天产生366,而不是我预期的0)导致我浪费了很多精力。

但查询计划没有改变。我想我需要回到整个事情的绘图板上。

10 个答案:

答案 0 :(得分:60)

这更加简洁:

where 
  datediff(day, date1, date2) = 0

答案 1 :(得分:19)

你几乎必须保持where子句的左侧清洁。所以,通常情况下,你会做类似的事情:

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

(有些人更喜欢DATEADD(d,1,@ activityDateMidnight) - 但这是相同的事情。)

TimeZone表虽然让事情变得复杂。你的片段有点不清楚,但它看起来像是.DateInTable在GMT中带有时区标识符,然后你要添加偏移量来与@activityDateMidnight进行比较 - 这是当地时间。我不知道ds.LocalTimeZone是什么。

如果是这种情况,那么你需要将@activityDateMidnight改为GMT。

答案 2 :(得分:4)

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

答案 3 :(得分:3)

请务必阅读Only In A Database Can You Get 1000% + Improvement By Changing A Few Lines Of Code,以确保优化程序在处理日期时可以有效地利用索引

答案 4 :(得分:2)

Eric Z Beard:

  

我将所有日期存储在GMT中。这是用例:事情发生在美国东部时间晚上11点,即格林威治标准时间第二天。我希望看到第一个活动,我在美国东部时间,所以我想看看11PM的活动。如果我只是比较原始GMT日期时间,我会想念一些事情。报告中的每一行都可以表示来自不同时区的活动。

是的,但是当你说你对2008年1月1日美国东部时间的活动感兴趣时:

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 AM'。然后,您可以在GMT中搜索:

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

有问题的事件以1月2日凌晨3:00的GMT EventTime存储。你甚至不需要在EventTable中使用TimeZone(至少为此目的)。

由于EventTime不在函数中,因此这是一个直接索引扫描 - 应该非常有效。使EventTime成为聚集索引,它将会飞行。 ;)

就个人而言,我会让应用程序在运行查询之前将搜索时间转换为GMT。

答案 5 :(得分:1)

这将从您的日期中删除时间组件:

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

答案 6 :(得分:1)

在这里,您可以选择各种选项。如果您使用的是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)

答案 7 :(得分: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上有轻微的开销。

答案 8 :(得分:0)

我会使用datepart的dayofyear函数:


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

请参阅日期部分参考here

答案 9 :(得分:0)

关于时区,还有一个理由将所有日期存储在一个时区(最好是UTC)。无论如何,我认为使用datediff,datepart和不同的内置日期函数的答案是你最好的选择。