检查两个日期是否包含给定月份

时间:2009-11-12 15:26:19

标签: sql sql-server date datetime

我的问题很简单......或者可能不是。我有一张包含两个日期的表格:

StartDate
EndDate

我有一个月的常数。例如:

DECLARE @MonthCode AS INT
SELECT  @MonthCode = 11  /* NOVEMBER */

我需要一个单一查询来查找其StartDate和EndDate包含给定月份的所有记录。例如:

/* Case 1 */ Aug/10/2009 - Jan/01/2010
/* Case 2 */ Aug/10/2009 - Nov/15/2009
/* Case 3 */ Nov/15/2009 - Jan/01/2010
/* Case 4 */ Nov/15/2009 - Nov/15/2009
/* Case 5 */ Oct/01/2010 - Dec/31/2010

第一个和最后一个案例需要特别注意:两个日期都在十一月之外但是越过它。

以下查询不处理案例1和案例5:

WHERE MONTH( StartDate ) = @MonthCode OR MONTH( EndDate ) = @MonthCode

以下查询也失败了,因为 8月< 11月和11月< 11月Jan = false

WHERE MONTH( StartDate ) = @MonthCode OR MONTH( EndDate ) = @MonthCode OR (
MONTH( StartDate ) < @MonthCode AND @MonthCode < MONTH( EndDate )
)

6 个答案:

答案 0 :(得分:5)

我知道您正在寻找一种方法来选择与11月交叉的所有范围,在任何一年

这是逻辑:

  • 如果范围是一年(例如2009年),则开始月份必须等于或等于11月和11月之后或等于11月的结束月份

  • 如果范围落后两年(例如2009-2010),则开始月份必须等于11月或等于11月或等于11月的结束月份

  • 如果范围为两年且差异超过1年(例如2008-2010),则11月总是包含在范围内(此处为2009年11月)

以伪代码翻译,条件是:

// first case
(
  (YEAR(StartDate)=YEAR(EndDate)) AND
  (MONTH(StartDate)<=MonthCode AND MONTH(EndDate)>=MonthCode)
)
OR
// second case
(
  (YEAR(EndDate)-YEAR(StartDate)=1) AND
  (MONTH(StartDate)<=MonthCode OR MONTH(EndDate)>=MonthCode)
)
OR
// third case
(
  YEAR(EndDate)-YEAR(StartDate)>1
)

答案 1 :(得分:2)

DECLARE @MonthCode AS INT
SELECT @MonthCode = 11  /* NOVEMBER */

declare @yourtable table(
    startdate datetime
    , enddate datetime
)
insert into @yourtable(
    startdate
    , enddate
)
(
select '8/10/2009', '01/01/2010'
union all
select '8/10/2009' , '11/15/2009'
union all
select '11/15/2009' , '01/01/2010'
union all 
select '11/15/2009' , '11/15/2009'
union all
select '10/01/2010' , '12/31/2010'
union all
select '05/01/2009', '10/30/2009'
)

select *
from @yourtable
where DateDiff(mm, startdate, enddate) > @MonthCode     -- can't go over 11 months without crossing date
    OR (Month(startdate) <= @MonthCode                  -- before Month selected
            AND (month(enddate) >=@MonthCode            -- after month selected
                OR year(enddate) > year(startdate)    -- or crosses into next year
                )
        )
    OR (Month(startdate) >= @MonthCode                  -- starts after in same year after month
            and month(enddate) >= @MonthCode            -- must end on/after same month assume next year
            and year(enddate) > year(startdate)
        )

答案 2 :(得分:1)

试试这个:

select * from Mytable
where 
month(StartDate) = @MonthCode or month(EndDate) = @MonthCode // Nov/15/2009 - Nov/15/2009
or
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(StartDate))))
between StartDate and EndDate // Oct/01/2010 - Dec/31/2010
or
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(EndDate))))
between StartDate and EndDate // Dec/01/2009 - Dec/31/2010 - tricky one

主要的想法是检查01.November.StartYear和01.November.EndYear日期的位置。

希望它有所帮助。

答案 3 :(得分:0)

过滤在月末之前开始的行,并在月初之后结束。 2009年10月:

select *
from YourTable
where StartDate < '2009-11-01' and EndDate >= '2009-10-01'

或者,只用月份作为输入:

declare @month datetime
set @month = '2009-10-01'

select *
from YourTable
where StartDate < dateadd(month,1,@month)
and EndDate >= @month

答案 4 :(得分:0)

您可以使用各种功能来实现此目标,例如DATEPARTDATETIFF。但是,真正的问题不是如何表达StartDate或EndDate落在给定月份的条件,而是如何以使查询有效的方式执行此操作。换句话说,如何以SARGable方式表达这一点。

如果您搜索一个小的更改表,任何低于10k页的内容,那么它没有那么大的差别,完全扫描可能是完全可以接受的。真正的问题是,如果表格的大小是显着的,并且完整扫描是不可接受的。

如果您没有任何StartDate或EndDate列的索引,它没有任何区别,标准是不可搜索的,查询将扫描整个表格。但是,如果StartDate和EndDate上有索引,那么表达条件的方式会有所不同。 DATETIME索引的关键部分是您必须将搜索表达为确切的日期范围。根据DATETIME字段将条件表示为函数将使条件不可搜索,从而导致全表扫描。因此,这些知识使自己能够以正确的方式搜索日期范围:

select ... from table
where StartDate between '20091101' and '20091201'
or EndDate between '20091101' and '20091201';

这也可以表示为:

select ... from table
where StartDate between '20091101' and '20091201'
union all
select ... from table 
where EndDate between '20091101' and '20091201'
and StartDate not between '20091101' and '20091201';

哪种查询效果更好取决于许多因素,例如表格大小和表格中实际数据的统计数据。

但是,您希望11月从任何年开始,此查询不会提供给您。解决这个问题的方法是反对程序员的所有本能:硬编码相关年份。大多数情况下,表格都有一小部分年份,在过去数据的4 - 5年范围内,计划3到3年,直到系统进行大修:

select ... from table
where StartDate between '20051101' and '20051201'
or EndDate between '20051101' and '20051201'
union all
select ... from table
where StartDate between '20061101' and '20061201'
or EndDate between '20061101' and '20061201'
union all
...
select ... from table
where StartDate between '20151101' and '20151201'
or EndDate between '20151101' and '20151201';

一年有12个月,写12个单独的程序。这听起来很疯狂吗?确实如此,但从SQL查询编译器和优化器角度来看是最佳的。如何维护这样的代码? 12个单独的过程,查询重复10次(如果你在StartDate和EndDate之间使用UNION删除OR,则为20次),120次重复代码,必须无意义。实际上,事实并非如此。使用代码生成来创建过程,如XML / XSLT,以便您可以轻松地更改和维护它。客户是否必须了解12个程序并拨打适当的程序?当然不是,它调用一个包装程序来区分@Month参数以调用正确的参数。

我认为,在事实发生之后,任何想要查看系统的人都会认为这个问题是由一群醉猴子写的。然而,在参数嗅探,索引SARGability和SQL DATETIME怪癖之间,结果是当涉及搜索日历间隔时,这是当今的最新技术。

哦,如果查询命中Index Tipping Point,它将使整个论点无论如何都是静音的......

<强>更新

如果你愿意牺牲一些存储空间,还有一个便宜的出路:StartMonth AS DATEPART(month, StartDate)EndDate AS DATEPART(month, EndDate)上的两个持久计算列,以及每个存储空间的索引WHERE StartMonth = @Month OR EndMonth = @Month (或者在两个查询之间再次使用UNION,一个用于开始一个用于结束,以删除OR)。

答案 5 :(得分:-1)

SQL Server 200/2005,您也可以这样做:

select 
   * 
from 
   table
where 
   datepart(m,startDate) = 11
   and datepart(m,EndDate) = 11

<强>更新 已移除and datepart(yyyy,startDate) = datepart(yyyy,endDate) 无论年份还是日期,都想要一个月?