【问题标题】:Check overlap of date ranges in MySQL检查 MySQL 中日期范围的重叠
【发布时间】:2011-02-02 12:12:18
【问题描述】:

此表用于存储会话(事件):

CREATE TABLE session (
  id int(11) NOT NULL AUTO_INCREMENT
, start_date date
, end_date date
);

INSERT INTO session
  (start_date, end_date)
VALUES
  ("2010-01-01", "2010-01-10")
, ("2010-01-20", "2010-01-30")
, ("2010-02-01", "2010-02-15")
;

我们不希望范围之间发生冲突。
假设我们需要插入一个从 2010-01-052010-01-25 的新会话。
我们想知道有冲突的会话。

这是我的查询:

SELECT *
FROM session
WHERE "2010-01-05" BETWEEN start_date AND end_date
   OR "2010-01-25" BETWEEN start_date AND end_date
   OR "2010-01-05" >= start_date AND "2010-01-25" <= end_date
;

结果如下:

+----+------------+------------+
| id | start_date | end_date   |
+----+------------+------------+
|  1 | 2010-01-01 | 2010-01-10 |
|  2 | 2010-01-20 | 2010-01-30 |
+----+------------+------------+

有没有更好的方法来获得它?


fiddle

【问题讨论】:

  • 你的第三个条件是错误的。它应该是"2010-01-05" &lt;= start_date AND "2010-01-25" &gt;= end_date。有关可视化,请参阅stackoverflow.com/a/28802972/632951。您当前的第三个条件永远不会评估,因为第一个(和第二个)条件已经涵盖了它。

标签: mysql range overlapping-matches


【解决方案1】:

我曾经写过一个日历应用程序,我遇到过这样的查询。我想我用过这样的东西:

... WHERE new_start < existing_end
      AND new_end   > existing_start;

更新这绝对可以工作((ns,ne,es,ee)=(new_start,new_end,existing_start,existing_end)):

  1. ns - ne - es - ee:不重叠也不匹配(因为 ne
  2. ns - es - ne - ee:重叠和匹配
  3. es - ns - ee - ne:重叠和匹配
  4. es - ee - ns - ne:不重叠也不匹配(因为 ns > ee)
  5. es - ns - ne - ee:重叠和匹配
  6. ns - es - ee - ne:重叠和匹配

这是fiddle

【讨论】:

  • 效果很好!但我认为@Pierre de LESPINAY 在他的查询中寻找包含范围:WHERE new_start = existing_start;
  • @OsvaldoM。如果他真的是,他会在两年前抱怨......
  • 我一个活动今天结束,另一个活动今天开始,它们重叠吗?在我看来,它们会重叠。但是SQL查询不会说:sqlfiddle.com/#!2/0a6fd/1/0
  • @soulmerge,实际上,他只是将= 添加到他的实际代码中,而不是费心抱怨它。
【解决方案2】:
SELECT * FROM tbl WHERE
existing_start BETWEEN $newStart AND $newEnd OR 
existing_end BETWEEN $newStart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

if (!empty($result))
throw new Exception('We have overlapping')

这3行sql子句涵盖了需要重叠的4种情况。

【讨论】:

  • 即使 OP 显然没有在寻找这个重叠的定义,这个答案也是问题名称描述的问题的最佳解决方案。我一直在寻找这种重叠,这是真正的重叠。
  • 我不确定您所说的“真正重叠”是什么意思,但@soulmerge 的最佳答案等同于 Lamy 的这个答案。我能够使用 Z3 Theorem Solver 来证明等价性:grantjenks.com/projects/equivalent-inequalities
【解决方案3】:

拉米的回答不错,不过可以再优化一点。

SELECT * FROM tbl WHERE
existing_start BETWEEN $newSTart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

这将捕获范围重叠的所有四种情况,并排除不重叠的两种情况。

【讨论】:

  • 除了这个和上面的另外两个,还有其他解决方案吗?
【解决方案4】:

我也遇到过类似的问题。我的问题是在一系列被阻止的日期之间停止预订。例如,5 月 2 日至 7 日之间的房产预订被阻止。我需要找到任何类型的重叠日期来检测和停止预订。我的解决方案类似于 LordJavac。

SELECT * FROM ib_master_blocked_dates WHERE venue_id=$venue_id AND 
(
    (mbd_from_date BETWEEN '$from_date' AND '$to_date') 
    OR
    (mbd_to_date BETWEEN  '$from_date' AND '$to_date')
    OR
    ('$from_date' BETWEEN mbd_from_date AND mbd_to_date)
    OR      
    ('$to_date' BETWEEN mbd_from_date AND mbd_to_date)      
)
*mbd=master_blocked_dates

如果它不起作用,请告诉我。

【讨论】:

    【解决方案5】:

    给定两个区间,如 (s1, e1) 和 (s2, e2),其中 s1 您可以像这样计算重叠:

    SELECT 
         s1, e1, s2, e2,
         ABS(e1-s1) as len1,
         ABS(e2-s2) as len2,
         GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0)>0 as overlaps,
         GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0) as overlap_length
    FROM test_intervals 
    

    如果一个间隔在另一个间隔内,也可以工作。

    【讨论】:

      【解决方案6】:

      最近我在同一个问题上苦苦挣扎,并以一个简单的步骤结束(这可能不是一个好方法或消耗内存)-

      SELECT * FROM duty_register WHERE employee = '2' AND (
      (
      duty_start_date BETWEEN {$start_date} AND {$end_date}
      OR
      duty_end_date BETWEEN {$start_date} AND {$end_date}
      )
      OR
      (
      {$start_date} BETWEEN duty_start_date AND duty_end_date
      OR
      {$end_date} BETWEEN duty_start_date AND duty_end_date)
      );
      

      这帮助我找到了日期范围重叠的条目。

      希望这对某人有所帮助。

      【讨论】:

        【解决方案7】:

        即使数据库中的 to-date 可能为空,您也可以覆盖所有日期重叠情况,如下所示:

        SELECT * FROM `tableName` t
        WHERE t.`startDate` <= $toDate
        AND (t.`endDate` IS NULL OR t.`endDate` >= $startDate);
        

        无论如何这将返回与新开始/结束日期重叠的所有记录。

        【讨论】:

          【解决方案8】:

          从性能角度来看,上述 Mackraken 的答案更好,因为它不需要多个 OR 来评估两个日期是否重叠。很好的解决方案!

          但是我发现在 MySQL 中你需要使用 DATEDIFF 而不是减号 -

          SELECT o.orderStart, o.orderEnd, s.startDate, s.endDate
          , GREATEST(LEAST(orderEnd, endDate) - GREATEST(orderStart, startDate), 0)>0 as overlaps
          , DATEDIFF(LEAST(orderEnd, endDate), GREATEST(orderStart, startDate)) as overlap_length
          FROM orders o
          JOIN dates s USING (customerId)
          WHERE 1
          AND DATEDIFF(LEAST(orderEnd, endDate),GREATEST(orderStart, startDate)) > 0;
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多