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

Posted

技术标签:

【中文标题】检查两个日期是不是包含给定月份【英文标题】:Check whether two dates contain a given month检查两个日期是否包含给定月份 【发布时间】:2009-11-12 15:26:18 【问题描述】:

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

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

第一种和最后一种情况需要特别注意:两个日期都在 11 月之外,但都超过了 11 月。

以下查询不处理情况 1 和 5:

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

以下查询也失败了,因为 Aug :

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

【问题讨论】:

听起来您确实需要某年中的某个月,该月与两个日期之间的时间跨度相交。您所说的方式听起来好像只有开始日期和结束日期需要有月份。 Chris 说了什么 - 鉴于上面的输入,您要选择哪些记录? 所有 5 个,因为它们包括或重叠了 11 月。年份无所谓。日期范围必须至少包括 11 月的一天。 这是一个有趣的练习,但它对您的业务有什么用处? 该表是一个带有开始/结束日期的“事件日历”表。事件可能发生在过去、现在或未来。我只需要一个按月浏览功能,它显示所有过去/现在/未来的事件,例如开始/结束/中间日期中的一月。 【参考方案1】:

我了解到您正在寻找一种方法来选择与 11 月相交的所有范围,在任何一年

逻辑如下:

如果范围为一年(例如 2009 年),则开始月份必须在 11 月之前或等于 11 月,结束月份必须在 11 月之后或等于 11 月

如果范围在随后的两个年份(例如 2009-2010),则开始月份必须在 11 月之前或等于 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
)

【讨论】:

比较日期时间而不是 MONTH() 和 YEAR(),你可以不用这三种方式或 我不这么认为。比较完整的日期时间将无法找到与 11 月相交的范围在任何一年【参考方案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)
        )

【讨论】:

【参考方案3】:

试试这个:

从 Mytable 中选择 * 在哪里 月(StartDate) = @MonthCode 或月(EndDate) = @MonthCode // Nov/15/2009 - Nov/15/2009 或者 dateadd(月,@MonthCode-1,convert(datetime,convert(varchar,year(StartDate)))) 在 StartDate 和 EndDate 之间 // Oct/01/2010 - Dec/31/2010 或者 dateadd(月,@MonthCode-1,convert(datetime,convert(varchar,year(EndDate)))) 在 StartDate 和 EndDate 之间 // Dec/01/2009 - Dec/31/2010 - 棘手的一个

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

希望对你有帮助。

【讨论】:

【参考方案4】:

筛选在月末之前开始并在月初之后结束的行。 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

【讨论】:

【参考方案5】:

您可以使用多种函数来实现此目的,例如DATEPART 和DATETIFF。然而,真正的问题不是如何表达 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-4 年,直到系统大修:

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为 Start 查询一个,为 End 查询一个,以删除 OR)。

【讨论】:

就优化而言,where 子句中还有其他项目会将行限制为最多 10 行——我需要进一步限制结果,这些结果属于指定范围内月。【参考方案6】:

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

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

更新: 删除and datepart(yyyy,startDate) = datepart(yyyy,endDate) 想要一个给定的月份而不考虑年份或日期?

【讨论】:

+1 是的,甚至是MONTH(StartDate) = 11 and YEAR(StartDate) = 2009 这是否适用于“2009 年 11 月 15 日 - 2010 年 1 月 1 日”的案例? 返回的唯一日期是:2009-11-15 - 2009-11-15

以上是关于检查两个日期是不是包含给定月份的主要内容,如果未能解决你的问题,请参考以下文章

如何检查表列中的三个日期是不是连续月份

Codeigniter-检查用户是不是存在于月份

MySQL 检查日期范围是不是在日期范围内

如何使用查询获取两个给定日期之间的月份列表?

检查过期日期是否无效 - 获取当前月份和年份没有“/”

检查给定日期是不是存在于从日期到今天之间