So this was one of my very first questions here, but I have a slight variation:
So I have two people whose schedules are in a database. The schedules simply record the start time, end time, and description of the various events/appointments for both users.
PersonA wants to trade appointments with PersonB. I want a MySQL query that will return all of them times that PersonB and PersonA can swap.
Originally the parameters of the query were to throw out any appointments of PersonB where there was overlap with PersonA and PersonB's appointment had to be the exact same length as the appointment PersonA wants to swap. I got some great advice on time arithmetic/geometry that helped me get the results I needed.
Now I want to change the 1-to-1 parameter so that the appointments don't have to be equal in length. So if PersonA wants to swap his Monday morning appointment (10:00 AM - 11:30 AM), the query will:
- Exclude any of PersonB's appointments that are during one of PersonA's appointments
- Include any of PersonB's appointments that are outside of PersonA's appointments
- Include the parts of PersonB's appointments that are while PersonA is free, but only show the free portion.
So if PersonA wants to swap the above appointment (again, Monday 10:00 AM - 11:30 AM), and PersonA has an appointment on Tuesday 1:00 PM to 3:00 PM and PersonB has an appointment on Tuesday from 12:00 PM to 4:00 PM, the query would return:
Possible_Swaps
==============
userID | Start | End | Description
PersonB | Tuesday, 12:00 PM | Tuesday 1:00 PM | Cooking
PersonB | Tuesday, 4:00 PM | Tuesday 5:00 PM | Cooking
In addition to any other possibilities. Is this too much to expect from the database? If so, any suggestions on how to at least get those shifts that are overlapping but have the times hanging over either side so that a PHP script could deal with them?
per searlea's request, here's a bit more context:
I kept saying appointments but I think I really meant "jobs" as in "work shifts". PersonA and PersonB work in the same office. In vcalendar, work shifts are usually referred to as "Events" but on occasion "Appointments" and I went with the latter as it sounds less like the two Persons are going to a fair.
So PersonA has a dish-washing shift on Monday from 10:00 to 11:30 AM. PersonB is cooking on Tuesday from 12:00PM to 5:00PM. PersonA really wants to see his brother before they leave town on Monday. He'd rather get all of Monday morning off, but he'd settle for getting an hour of shift off .
So in my old model (brought up in my very first question here), I was looking for any shifts where there was no overlap and where the shifts were equal in time. But that has two problems:
If I need someone to cover my 2 hour shift on Tuesday and I work for 4 hours on Thursday, and Joe works for 8 hours on Thursday, I could swap two of his hours and he could leave a bit early and I can stay a bit later.
If I have a two hour shift, but I'd gladly trade an hour of it just to make it to the airport on time, I want to know if such and such comes in an hour earlier than me later in the week so I can take that part of his shift.
Long story short (too late), I want what is apparently known as the relative complement of PersonA's shifts to PersonB (basically any times that PersonB is working and PersonA is not, regardless of whether the shifts overlap at some other point.)
Ideally, I would get a set of results that included the bits that PersonB was working and PersonA was not (the two 1 hour shifts mentioned above), as well as the entire shift (with a special tag to indicate it is not available as a whole) so that PersonA would see that he was covering part of a shift and not get confused and think that PersonB just happened to be working two one hour shifts.
This is all starting to sound a bit complicated. Basically I want PersonB's shifts to be be blue, PersonA's shifts to be yellow, and I want the database to return all of the parts that are not green.
Task
Return all the intervals of two different users except parts where they overlap.
Table and test data
Test results
Solution
I used feature of MySQL called User-Defined Variables to achieve the goal with the following query:
Basic idea is to list the table twice sorted by time so that every record appear twice. Once for the start time and then for the end time. Then I'm using user-defined variables to keep track of the state while traversing records and return only 'end time' records with start time and end time adjusted for overlapping intervals.
Assumptions
Only assumption is that no interval of person x does overlap with another interval of the same person.
Behavior
Few cases, and their results:
Caveats
I must have used unix timestamp since it my mysql server could not make comparison between DATETIME kept in user-defined variable and something else.
Pros & Cons
It does it's job in single pass without any joins so it should take O(N) time. It cannot retrieve all the parts of interval of person A cut out by enclosed intervals of person B. It uses MySQL specific functionality.
Let
$shift_id
be the id of the shift that your user wants to swap.Tested with:
Results:
This shows all shifts for which any portion(s) can be swapped, including how much total time (in seconds) is swappable. The final column,
conflict_times
, shows the times for which the swapping user is already scheduled to work. It should be easy for the application to extract the available times from that; it's possible, but very tricky, in MySQL.This selects Ondra's events which can fit into a gap in Zizka's diary.
Edited: Originally it was an intersect, but if you want the relative complement, this is enough.
For the reference a code snipped which I used recently. It can be used to check for overlapping date ranges. It is written in Ruby on Rails, but the idea (the SQL Statement) can easily be translated to other languages)
As usual with named scopes this scope can be reused in combination with any other scopes.