比较日期范围

时间:2020-03-06 14:49:24  来源:igfitidea点击:

在MySQL中,如果我有日期范围的列表(范围开始和范围结束)。例如

10/06/1983 to 14/06/1983
15/07/1983 to 16/07/1983
18/07/1983 to 18/07/1983

我想检查另一个日期范围是否包含列表中已经存在的任何范围,我该怎么做?

例如

06/06/1983 to 18/06/1983 = IN LIST
10/06/1983 to 11/06/1983 = IN LIST
14/07/1983 to 14/07/1983 = NOT IN LIST

解决方案

这是一个经典问题,如果我们逆转逻辑,实际上会更容易。

让我给你举个例子。

我将在此处发布一个时间段,以及其他时间段的所有不同变体以某种方式重叠。

|-------------------|          compare to this one
               |---------|                contained within
           |----------|                   contained within, equal start
                   |-----------|          contained within, equal end
           |-------------------|          contained within, equal start+end
     |------------|                       not fully contained, overlaps start
                   |---------------|      not fully contained, overlaps end
     |-------------------------|          overlaps start, bigger
           |-----------------------|      overlaps end, bigger
     |------------------------------|     overlaps entire period

另一方面,让我发布所有不重叠的内容:

|-------------------|          compare to this one
     |---|                                ends before
                                 |---|    starts after

因此,如果我们简单地将比较简化为:

starts after end
ends before start

那么我们将找到所有不重叠的那些,然后我们将找到所有不匹配的时间段。

对于最后一个NOT IN LIST示例,我们可以看到它与这两个规则匹配。

我们需要确定以下时间段是否在范围之内或者之外:

|-------------|
   |-------|                       equal end with start of comparison period
                         |-----|   equal start with end of comparison period

如果表具有名为range_end和range_start的列,则以下是一些简单的SQL来检索所有匹配的行:

SELECT *
FROM periods
WHERE NOT (range_start > @check_period_end
           OR range_end < @check_period_start)

请注意其中的NOT。由于这两个简单规则找到了所有不匹配的行,因此简单的NOT会将其反转为:如果它不是不匹配的行之一,则必须是匹配的行之一。

在这里应用简单的反转逻辑来摆脱NOT,我们最终会得到:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start

以范围为06/06/1983到18/06/1983的示例为例,并假设范围有名为start和end的列,则可以使用类似这样的子句

where ('1983-06-06' <= end) and ('1983-06-18' >= start)

即检查测试范围的开始在数据库范围的结束之前,并且测试范围的结束在数据库范围的开始之后或者之上。

如果RDBMS支持OVERLAP()函数,那么这变得微不足道-不需要本地解决方案。 (在Oracle中,它显然起作用,但未记录在案)。

在预期结果中,我们说

1983年6月6日至1983年6月18日=列表中

但是,此期间不包含也不包含在期间表(未列出!)中的任何期间。但是,它与10/06/1983到14/06/1983重叠。

我们可能会发现Snodgrass的书(http://www.cs.arizona.edu/people/rts/tdbbook.pdf)很有用:它早于mysql,但是时间的概念没有改变;-)