SQL Server:仅比较月份和日期 - SARGable

Posted

技术标签:

【中文标题】SQL Server:仅比较月份和日期 - SARGable【英文标题】:SQL Server: compare only month and day - SARGable 【发布时间】:2018-02-14 08:05:47 【问题描述】:

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

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

可以有 2 种类型的参考进行比较:固定的给定日期和今天(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(),但它们都以索引扫描结束。

【问题讨论】:

您能否在存储“标准化”日期的表中添加一个计算列(这样它与您当前的列相同,但所有日期都在 2000 年)? @Damien_The_Unbeliever 这是唯一能想到的选项,对您选择闰年有好处。 @Damien_The_Unbeliever 由于生产表的大小(大约 2000 万行)并且它也是用户生成的表,这样做是不可行的。 好吧,不幸的是,datetime(或 datetime2)是自过去某个固定日期以来的间隔计数。假设是自 1900 年以来的日子(datetime 的整数部分)。 42778 和 43143 代表两年中的同一个月和同一天,但无法索引此数据以进行可以知道的查询(闰日也确实搞砸了) 您可以在上面使用Calendar Table 和JOIN。确保您的日历具有月份编号和日期编号列,并且包含 WHERE CT.MonthNum = @MonthValue AND CT.DayNum = @DayValue 的查询是完全可搜索的。 (CT 是日历表)。 【参考方案1】:

添加到我在Calendar Table 上发表的评论。

这可能是获取 SARGable 查询的最简单方法。正如您所发现的,MONTH([YourColumn])DATEPART(MONTH,[YourColumn]) 都会导致您的查询变得不可搜索。

考虑到您的所有列,至少在您的示例数据中,都有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 的查询。

如果您想处理闰年,您可以使用 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?你确实知道这是做什么的,对吧..?除非您对脏读和幽灵数据感到满意?

【讨论】:

使用日历表,您可以添加额外的列来处理闰年,例如 LeapDayDay 的每个日期相同,除了 2 月 29 日,它包含 28,@ 987654340@ 表示闰年等 @PanagiotisKanavos 也可以。 :) CASE 表达式只是一个示例,因为链接的文章更多是对日历表的介绍。

以上是关于SQL Server:仅比较月份和日期 - SARGable的主要内容,如果未能解决你的问题,请参考以下文章

在SQL Server表查询中查找不同的用户

SQL Server 中的条件联合,直到到达特定日期

在 SQL Server 2008 中从月份和年份创建日期

SQL Server 查找接近服务日期和月份的行

休眠查询日期仅比较月份

SQL Server 2008 仅在月份和年份之间选择数据