【问题标题】:Comparing date ranges比较日期范围
【发布时间】:2010-09-13 16:48:43
【问题描述】:

在 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

【问题讨论】:

标签: sql mysql date


【解决方案1】:

这是一个经典的问题,如果你把逻辑颠倒过来,实际上会更容易。

让我举个例子。

我将在这里发布一段时期,以及以某种方式重叠的其他时期的所有不同变化。

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

【讨论】:

  • 我们需要一个“包含 ACII 图表”标志的答案,让您可以多次投票
  • 可能是我在 SO 上看到的 5 个最佳答案之一。很好的问题解释,很好的解决方案演练,以及......图片!
  • 如果我可以多次投票,我会的。对出现的常见问题进行了出色、清晰和简明的解释,这是我很少见过的解决方案得到如此充分的解释!
  • 很好的答案!我要添加的唯一一件事 - 关于决定是否包含端点 - 如果您在一侧使用封闭间隔而在另一侧使用开放间隔,那么一切都会变得更干净。例如。范围的开头包含在点中,而范围的结尾不包含。尤其是当您处理日期和各种分辨率的时间组合时,一切都会变得更简单。
  • 好答案。这也被描述为Allen's Interval Algebra。我有一个类似的answer,并与一位评论员进行了多少不同的比较。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    如果您的 RDBMS 支持 OVERLAP() 函数,那么这将变得微不足道 - 不需要自行开发的解决方案。 (在 Oracle 中它显然可以工作,但没有记录)。

    【讨论】:

    • 史诗般的解决方案。工作正常。这是 Oracle 中 2 个日期范围 (s1,e1) 和 (s2,e2) 的语法:select 1 from dual where (s1,e1) 与 (s2,e2) 重叠;
    【解决方案4】:

    在你的预期结果中你说

    06/06/1983 至 18/06/1983 = 在列表中

    但是,此期间不包含也不包含在您的期间表(不是列表!)中的任何期间。但是,它确实与 1983 年 6 月 10 日至 1983 年 6 月 14 日期间重叠。

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

    【讨论】:

      【解决方案5】:

      我在 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 ;
      

      【讨论】:

        【解决方案6】:

        看看下面的例子。对你有帮助。

            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
        

        【讨论】:

          【解决方案7】:
          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;
          

          【讨论】:

            【解决方案8】:

            在 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];
            

            【讨论】:

              【解决方案9】:

              另一种使用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)
              

              【讨论】:

                【解决方案10】:
                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!感谢您提供此代码 sn-p,它可能会提供一些即时帮助。一个正确的解释would greatly improve 其教育价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有类似但不相同的问题的读者更有用。请edit您的答案添加解释,并说明适用的限制和假设。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2015-06-19
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-06-29
                • 1970-01-01
                相关资源
                最近更新 更多