比较日期范围


116

在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

Answers:


438

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

让我举一个例子。

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

           |-------------------|          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

那么您将找到所有不重叠的部分,然后找到所有不匹配的期间。

对于最后一个“不在清单中”示例,您可以看到它与这两个规则匹配。

您需要确定以下时间段是否在您的范围之内或之外:

           |-------------|
   |-------|                       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

45
我们需要一个“包含ACII图”标记来回答,以便您可以多次投票
Jonny Buchanan

29
大概是我在SO上看到的5个最佳答案之一。对问题的详尽解释,很好的解决方案演练,还有图片!
davidavr

10
如果我可以多次投票赞成,我会的。对即将出现的一个常见问题进行了很好,清晰而简明的解释,这一解决方案我从未见过这么好解释!
ConroyP

2
好答案!我唯一要添加的内容-关于确定是否包含端点的信息-如果一侧为封闭间隔,另一侧为开放间隔,则一切工作都将更加干净。例如,范围的起点包括该点,而范围的终点则不包括。特别是当您处理日期和各种分辨率的时间的组合时,一切都会变得更加简单。

1
好答案。这也称为艾伦区间代数。我有一个类似的答案,并且就一个评论员进行了多少次不同的比较进行了激烈的争夺。
Jonathan Leffler

8

以您的范围为06/06/1983到18/06/1983的示例为例,并假设您有用于范围的名为startend的列,则可以使用这样的子句

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

例如,检查测试范围的起点在数据库范围的终点之前,并且测试范围的终点在数据库范围的起点之后或之上。


4

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


1
史诗般的解决方案。工作良好。这是Oracle中2个日期范围(s1,e1)和(s2,e2)的语法:从对偶(s1,e1)与(s2,e2)重叠的对偶中选择1;
ihebiheb

0

在您的预期结果中,您说

1983年6月6日至1983年6月18日=清单

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

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


0

我创建了函数来处理MySQL中的此问题。只需将日期转换为秒即可使用。

DELIMITER ;;

CREATE FUNCTION overlap_interval(x INT,y INT,a INT,b INT)
RETURNS INTEGER DETERMINISTIC
BEGIN
DECLARE
    overlap_amount INTEGER;
    IF (((x <= a) AND (a < y)) OR ((x < b) AND (b <= y)) OR (a < x AND y < b)) THEN
        IF (x < a) THEN
            IF (y < b) THEN
                SET overlap_amount = y - a;
            ELSE
                SET overlap_amount = b - a;
            END IF;
        ELSE
            IF (y < b) THEN
                SET overlap_amount = y - x;
            ELSE
                SET overlap_amount = b - x;
            END IF;
        END IF;
    ELSE
        SET overlap_amount = 0;
    END IF;
    RETURN overlap_amount;
END ;;

DELIMITER ;

0

看下面的例子。这将对您有所帮助。

    SELECT  DISTINCT RelatedTo,CAST(NotificationContent as nvarchar(max)) as NotificationContent,
                ID,
                Url,
                NotificationPrefix,
                NotificationDate
                FROM NotificationMaster as nfm
                inner join NotificationSettingsSubscriptionLog as nfl on nfm.NotificationDate between nfl.LastSubscribedDate and isnull(nfl.LastUnSubscribedDate,GETDATE())
  where ID not in(SELECT NotificationID from removednotificationsmaster where Userid=@userid) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo

0

在MS SQL上尝试


WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, [ending date]) - DATEDIFF(DAY, [start date], [ending date]), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range 
WHERE DATEADD(DAY, 1, calc_date) <= [ending date])
SELECT  P.[fieldstartdate], P.[fieldenddate]
FROM date_range R JOIN [yourBaseTable] P on Convert(date, R.calc_date) BETWEEN convert(date, P.[fieldstartdate]) and convert(date, P.[fieldenddate]) 
GROUP BY  P.[fieldstartdate],  P.[fieldenddate];

0
CREATE FUNCTION overlap_date(s DATE, e DATE, a DATE, b DATE)
RETURNS BOOLEAN DETERMINISTIC
RETURN s BETWEEN a AND b or e BETWEEN a and b or  a BETWEEN s and e;

0

使用BETWEEN sql语句的另一种方法

期间包括:

SELECT *
FROM periods
WHERE @check_period_start BETWEEN range_start AND range_end
  AND @check_period_end BETWEEN range_start AND range_end

排除期间:

SELECT *
FROM periods
WHERE (@check_period_start NOT BETWEEN range_start AND range_end
  OR @check_period_end NOT BETWEEN range_start AND range_end)

-2
SELECT * 
FROM tabla a 
WHERE ( @Fini <= a.dFechaFin AND @Ffin >= a.dFechaIni )
  AND ( (@Fini >= a.dFechaIni AND @Ffin <= a.dFechaFin) OR (@Fini >= a.dFechaIni AND @Ffin >= a.dFechaFin) OR (a.dFechaIni>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )

欢迎使用Stack Overflow!感谢您提供此代码段,它可能会立即提供帮助。通过说明为什么这是一个解决问题的好方法,适当的解释将大大提高其教育价值,并且对将来有相似但不相同问题的读者更有用。请编辑您的答案以添加说明,并指出适用的限制和假设。
Toby Speight
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.