【问题标题】:Checking a table for time overlap?检查表格的时间重叠?
【发布时间】:2011-07-04 12:31:31
【问题描述】:

我有一个包含以下字段的 MySQL 表:

  • 姓名
  • 开始时间
  • 结束时间

starttimeendtime 是 MySQL TIME 字段(不是 DATETIME)。我需要一种定期“扫描”表格的方法,以查看表格内的时间范围是否有任何重叠。如果有来自10:00-11:00 的事件和来自10:30-11:30 的另一个事件,我希望收到时间重叠存在的警报。

没什么特别的,我只想知道是否存在重叠。

我将使用 PHP 来执行此操作。

【问题讨论】:

    标签: mysql sql time range overlap


    【解决方案1】:

    这是我多年前找到答案的查询模式:

    SELECT *
    FROM mytable a
    JOIN mytable b on a.starttime <= b.endtime
        and a.endtime >= b.starttime
        and a.name != b.name; -- ideally, this would compare a "key" column, eg id
    

    要找到“任何重叠”,您需要将时间范围的相反端相互比较。这是我必须拿出笔和纸并画出相邻范围才能意识到边缘情况归结为这种比较的东西。


    如果您想防止任何行重叠,请将此查询的变体放入触发器中:

    create trigger mytable_no_overlap
    before insert on mytable
    for each row
    begin
      if exists (select * from mytable
                 where starttime <= new.endtime
                 and endtime >= new.starttime) then
        signal sqlstate '45000' SET MESSAGE_TEXT = 'Overlaps with existing data';
      end if;
    end;
    

    【讨论】:

    • 如果你有两个同名的事件怎么办?你需要一个UNIQUE 约束在你的表中的某个地方,可能是一个 *gulp* 自动递增 ID。
    • 我通常会删除运算符的 = 部分,因为对于大多数调度目的,我认为 10-11 不会与 11-12“重叠”。
    • 01:00 - 05:00 似乎与 04:00 - 24:00 没有重叠(因为 mysql 将 24:00 显示为 00:00)
    • 也不适用于这种奇怪的情况(但应该):"17:00:00-03:00:00", "14:00:00-01:00:00",问题是时间循环,并且很难控制它给出的数字,尤其是在转换时区时
    • @timo 如果您的日程安排跨越或超过午夜,请在根据我的查询进行比较之前将时间转换为纪元秒
    【解决方案2】:

    我想要一个通用函数来检查日期的两个时间范围是否重叠,这也适用于时间表在午夜之前开始并在午夜之后结束的情况,例如“17:00:00-03:00:00”和“14 :00:00-01:00:00" 应该重叠,所以我修改了solution by Bohemian

    你使用这个函数如下

    SELECT func_time_overlap("17:00:00","03:00:00", "14:00:00","01:00:00")
    

    或者在你的情况下是这样的

    SELECT *
    FROM mytable a
    JOIN mytable b ON (
        a.name != b.name 
        AND func_time_overlap(a.starttime, a.endtime, b.starttime, b.endtime)
    );
    

    这是函数定义

    CREATE FUNCTION `func_time_overlap`(a_start TIME, a_end TIME, b_start TIME, b_end TIME) 
    RETURNS tinyint(1) 
    DETERMINISTIC
    BEGIN
    
    -- there are only two cases when they don't overlap, but a lot of possible cases where they do overlap
    
    -- There are two time formats, one is an interval of time that can go over 24 hours, the other is a daily time format that never goes above 24 hours
    -- by default mysql uses TIME as an interval
    -- this converts a TIME interval into a date time format
    
    -- I'm not using `TIME(CAST(a_start AS DATETIME));` to convert the time interval to a time
    -- because it uses the current day by default and might get affected by the timezone settings of the database, 
    -- just imagine the next day having the DST change.
    -- although the CAST should work fine if you use UTC
    
    IF a_start >= 24 THEN 
        SET a_start = TIME(CONCAT(MOD(HOUR(a_start), 24),':',MINUTE(a_start),':',SECOND(a_start))); 
    END IF;
    
    IF b_start >= 24 THEN 
        SET b_start = TIME(CONCAT(MOD(HOUR(b_start), 24),':',MINUTE(b_start),':',SECOND(b_start))); 
    END IF;
    
    IF a_end > 24 THEN 
        SET a_end = TIME(CONCAT(MOD(HOUR(a_end), 24),':',MINUTE(a_end),':',SECOND(a_end))); 
    END IF;
    
    IF b_end > 24 THEN 
        SET b_end = TIME(CONCAT(MOD(HOUR(b_end), 24),':',MINUTE(b_end),':',SECOND(b_end))); 
    END IF;
    
    
    -- if the time range passes the midnight mark, then add 24 hours to the time
    IF a_start >= a_end THEN 
        SET a_end = a_end + INTERVAL 24 HOUR; 
    END IF;
    
    IF b_start >= b_end THEN 
        SET b_end = b_end + INTERVAL 24 HOUR; 
    END IF;
    
    RETURN a_start < b_end AND a_end > b_start;
    
    
    END
    

    我没有使用TIME(CAST(a_start AS DATETIME)); 将时间间隔转换为时间,因为它默认使用当前日期,并且可能会受到数据库时区设置的影响,想象一下第二天 DST 更改。

    如果您的数据库使用 UTC 时区(应该如此),那么您可以使用它

    IF a_start >= 24 THEN 
        SET a_start = TIME(CAST(a_start AS DATETIME)); 
    END IF;
    
    IF b_start >= 24 THEN 
        SET b_start = TIME(CAST(b_start AS DATETIME)); 
    END IF;
    
    IF a_end > 24 THEN 
        SET a_end = TIME(CAST(a_end AS DATETIME));
    END IF;
    
    IF b_end > 24 THEN 
        SET b_end = TIME(CAST(b_end AS DATETIME));
    END IF;
    

    【讨论】:

      【解决方案3】:

      试试这个:

      declare @tempTbl table(RecID)
      
          insert into @tempTbl
          Select RecID
          from 
          (
          Select t.RecID from Table1 t,Table1 t1
          where t.StartTime between t1.StartTime AND t1.EndTime
          AND t.RecID <> t1.RecID  
      
          )
      

      【讨论】:

      • 实际上这行不通。考虑开始时间 before 范围,但结束时间 in 范围...这是重叠的,但您的查询找不到它
      • @Bohemian 它实际上会起作用:查询会检查两个范围,对于 other 范围,在这种情况下查询实际上会给出结果
      【解决方案4】:

      试试这个,它对我有用

      SELECT * from Shedulles a 
      where exists 
      ( select 1 from Shedulles b 
          where 
          a.ShedulleId != b.ShedulleId 
          and ( a.DateFrom between b.DateFrom and b.DateTo 
          or a.DateTo between b.DateFrom and b.DateTo 
          or b.DateFrom between a.DateFrom and a.DateTo ) 
          and a.DateFrom != b.DateTo 
          and b.DateFrom != a.DateTo 
      );
      

      或者这个

      SELECT DISTINCT a.* FROM Shedulles a
      JOIN Shedulles b 
          ON 
          a.ShedulleId != b.ShedulleId 
          and ( a.DateFrom between b.DateFrom and b.DateTo 
          or a.DateTo between b.DateFrom and b.DateTo 
          or b.DateFrom between a.DateFrom and a.DateTo ) 
          and a.DateFrom != b.DateTo 
          and b.DateFrom != a.DateTo 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-08-29
        • 1970-01-01
        • 1970-01-01
        • 2012-07-19
        • 2019-03-19
        • 1970-01-01
        相关资源
        最近更新 更多