SQL Server:仅比较月和日 - SARGable

时间:2018-02-14 08:05:48

标签: sql sql-server datetime query-performance

我有一个存储datetime列的表,该列已编入索引。我正试图找到一种比较月和日的方法(完全忽略年份)。

仅供记录,我想说我已经在使用MONTH()DAY()了。但是我遇到的问题是我当前的实现使用了Index Scan而不是Index Seek,因为在这两个函数中直接使用了这个列来获取月份和日期。

可以有两种类型的参考用于比较:固定的给定日期和今天的(GETDATE())。日期将根据时区进行转换,然后提取月份和日期,例如

DECLARE @monthValue DATETIME = MONTH(@ConvertDateTimeFromServer_TimeZone);
DECLARE @dayValue DATETIME = DAY(@ConvertDateTimeFromServer_TimeZone); 

另一点是该列存储具有不同年份的日期时间,例如

1989-06-21 00:00:00.000
1965-10-04 00:00:00.000
1958-09-15 00:00:00.000
1965-10-08 00:00:00.000
1942-01-30 00:00:00.000

现在问题来了。如何创建SARGable查询以获取表中与给定月份和日期匹配的行,而不管年份如何,但也不涉及任何函数中的列?网络上的现有示例使用年份和/或日期范围,对我而言根本没有帮助。

示例查询:

Select t0.pk_id 
From dob t0 WITH(NOLOCK) 
WHERE ((MONTH(t0.date_of_birth) = @monthValue AND DAY(t0.date_of_birth) = @dayValue))

我还尝试了DATEDIFF()DATEADD(),但他们最终都进行了索引扫描。

1 个答案:

答案 0 :(得分:2)

Calendar Table上添加我所做的评论。

这可能是获取SARGable查询的最简单方法。正如您所发现的那样,MONTH([YourColumn])DATEPART(MONTH,[YourColumn])都会导致您的查询变为非SARG。

考虑到您的所有列(至少在您的示例数据中)的时间为00:00:00,这对我们有利,因为它们实际上只是日期。这意味着我们可以使用以下内容轻松JOIN到日历表中

SELECT dob.[YourColumn]
FROM dob
     JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate;

现在,如果我们使用上述文章中的表格,您将创建一些额外的列(MonthNoCDay,但是,您可以随意调用它们)。然后,您可以将这些列添加到查询中:

SELECT dob.[YourColumn]
FROM dob
     JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate
WHERE CT.MonthNo = @MonthValue
  AND CT.CDay = @DayValue;

正如您所看到的,这是一个更具SARGable的查询。

如果您想处理Leap Years,可以使用CASE表达式添加更多逻辑:

SELECT dob.[YourColumn]
FROM dob
     JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate
WHERE CT.MonthNo = @MonthValue
  AND CASE WHEN DATEPART(YEAR, GETDATE()) % 4 != 0 AND CT.CDat = 29 AND CT.MonthNo = 2 THEN 28 ELSE CT.Cdat END = @DayValue;

这将2月29日某人的生日视为2月28日,即非闰年(DATEPART(YEAR, GETDATE()) % 4 != 0)。

也许值得注意的是,将DateOfBirth列更改为date可能是值得的。出生日期不是在给定时间,仅在给定日期;这意味着您的日历表上没有从datetimedate的隐式转换。

编辑:另外,刚才注意到,你为什么要使用NOLOCK?你做知道那是做什么的,对吧..?除非你对脏读和鬼数据感到满意吗?