vendredi 27 février 2015

SQL - Algorithm for finding availability of a resource


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));


Visual JSFiddle demo


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.





Aucun commentaire:

Enregistrer un commentaire