I'm having trouble creating a mysql compatible algorithm for this.
Background
App with mysql, perl and JS. It's a booking system where each booking
is comprised of a start
, end
and qty
. Start and end are timestamps.
Schema simplified to a single table:
| bookings
|-------------------
| id | pkey
| start | timestamp
| end | timestamp
| qty | int
Question
In SQL, how do you check how many resources are booked at once for a given timeRange
? Code with explanation or an algorithm that is SQL compatible both work.
So, for the following schedule:
09:00 ----- <-|
09:30 | | | A maximum of 12 are booked at once during this range
10:00 |x7 | |
10:30 ----- ----- ----- |
11:00 | | | | |
11:30 |x2 | |x10| <-|
12:00 | | | |
12:30 ----- -----
I should get 12 since the x2 and x10 bookings don't overlap with the x7 booking, so there are never more than 12 items booked at once between 9:00
and 11:30
.
Progress
--It's been heavily shrunk to show just the relevant part, so it might have some errors
SELECT coalesce(max(qtyOverlap.sum),0) booked
FROM (
SELECT coalesce(sum(b2.qty),0) sum
FROM booking b1
LEFT JOIN (
SELECT b.qty, b.tStart, b.tEnd FROM booking b
) b2
ON b1.tStart < b2.tEnd AND
b1.tEnd > b2.tStart AND
b2.tStart < '2015-02-19 16:30:00' AND
b2.tEnd > '2015-02-19 06:00:00'
WHERE
b1.tStart < '2015-02-19 16:30:00' AND
b1.tEnd > '2015-02-19 06:00:00'
GROUP BY b1.id
) qtyOverlap
GROUP BY qtyOverlap.itemId
Which is this algorithm:
Max of
For each booking that overlaps given timeRange
return sum of
each booking that overlaps this booking and given timeRange
In the schedule above this would be:
max([7],[2+10],[10+2]) = 12
But given a schedule like:
09:00 ----- <-|
09:30 | | | A maximum of 17 are booked at once during this range, not 19
10:00 |x7 | |
10:30 | | ----- |
11:00 ----- | | |
11:30 ----- |x10| <-|
12:00 |x2 | | |
12:30 ----- -----
This gives:
max([7+10],[2+10],[10+7+2]) = 19
Which is wrong.
The only way I can think of to fix this is to use recursion (which isn't mysql compatible afaik).
It would look something like (in working JS code)
function getOverlaps(bookings,range) {
return bookings.filter(function(booking){
return isOverLapping(booking,range);
});
}
function isOverLapping(a, b) {
return (a.start < b.end && a.end > b.start);
}
function maxSum(booking, overlaps) { // main recursive function
var currentMax = 0;
var filteredOverlaps = getOverlaps(overlaps,booking);
for (var i = 0; i < filteredOverlaps.length; i++) {
currentMax = Math.max(
maxSum(filteredOverlaps[i], removeElement(filteredOverlaps,i)),
currentMax
);
}
return currentMax + booking.qty;
}
function removeElement(array,i){
var clone = array.slice(0)
clone.splice(i,1);
return clone;
}
var maxBooked = maxSum(timeRange, getOverlaps(bookings,timeRange));
Any way to do this in SQL? (any reasonable way, that is)
Update
I just tried to use a stored procedure recursion emulation method as documented here. But part way through implementing it, I tried it with the demo data and decided the performance was far too terrible. Actually, it just needed indexing. Now it's just 'kinda' bad.