Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking a table for time overlap?

Tags:

I have a MySQL table with the following fields:

  • name
  • starttime
  • endtime

starttime and endtime are MySQL TIME fields (not DATETIME). I need a way to periodically "scan" the table to see if there are any overlaps in time ranges within the table. If there is an event from 10:00-11:00 and another from 10:30-11:30, I want to be alerted of the presence of the time overlap.

Nothing fancy really, all I want to know whether an overlap exists or not.

I'm going to be using PHP to execute this.

like image 623
Danish M. Avatar asked Jul 04 '11 12:07

Danish M.


People also ask

How do you calculate overlapping time?

Overlap = min(A2, B2) - max(A1, B1) + 1. In other words, the overlap of two integer intervals is a difference between the minimum value of the two upper boundaries and the maximum value of the two lower boundaries, plus 1.

How do you know if two time ranges overlap?

1) Sort all intervals in increasing order of start time. This step takes O(nLogn) time. 2) In the sorted array, if start time of an interval is less than end of previous interval, then there is an overlap.

How do you find overlapping time intervals in SQL?

Basically, a period can be represented by a line fragment on time axis which has two boundaries; starttime and endtime. To claim two time periods to be overlapping, they must have common datetime values which is between lower and upper limits of both periods.


2 Answers

This is a query pattern for which I found the answer many years ago:

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 

To find "any overlap", you compare the opposite ends of the timeframe with each other. It's something I had to get a pen and paper out for and draw adjacent ranges to realise that the edge cases boiled down to this comparison.


If you want to prevent any rows from overlapping, put a variant of this query in a trigger:

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; 
like image 72
Bohemian Avatar answered Oct 01 '22 18:10

Bohemian


I wanted a generic function to check if two time ranges for days overlap which would also work with cases where the schedule starts before midnight and ends after, like "17:00:00-03:00:00" and "14:00:00-01:00:00" should overlap, so I modified the solution by Bohemian

you use this function as follows

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

or in your case like this

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

Here is the function definition

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 

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.

If your database is using UTC timezone (as it should) then you can use this

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; 
like image 22
Timo Huovinen Avatar answered Oct 01 '22 20:10

Timo Huovinen