I have a booking system that works with double bookings. A person books a room for a tsrange, and then can book appointments on top of that room reservation. I was able to make the constraints work fine, of course the tsrange of the appointments must be contained on the reservation tsrange.
Now, I need a query that returns a tsranges[] of the ranges that a reservation is idle, ie no appointments have been made at that time yet, but I am not sure how to do that...Well, I have a rough idea of how I might implement this in plpgsql looping over the appointments, but I wonder if there is a more elegant solution with plain SQL, maybe using a recursive CTE or a window function?
For example consider I have a reservation on range: '[2010-01-01 08:00, 2010-01-01 18:00)'
.
And the following appointments on that reservation: '[2010-01-01 08:00, 2010-01-01 09:00)';'[2010-01-01 11:00, 2010-01-01 12:00)';'[2010-01-01 14:00, 2010-01-01 17:00)'
The output of such function be something like: '[2010-01-01 09:00, 2010-01-01 11:00)','[2010-01-01 12:00, 2010-01-01 14:00)','[2010-01-01 17:00, 2010-01-01 18:00)'
Here is a sample dbfiddle with the simplified schema :
create table reservation (
id numeric,
room_id numeric,
during tsrange
);
create table appointment (
id serial,
on_reservation numeric,
during tsrange
);
insert into reservation (id, room_id, during)
VALUES (1, 1, '[2010-01-01 08:00, 2010-01-01 18:00)');
insert into appointment (id, on_reservation, during)
VALUES (2, 1, '[2010-01-01 08:00, 2010-01-01 09:00)');
insert into appointment (id, on_reservation, during)
VALUES (3, 1, '[2010-01-01 10:00, 2010-01-01 12:00)');
insert into appointment (id, on_reservation, during)
VALUES (4, 1, '[2010-01-01 14:00, 2010-01-01 17:00)');
I am still not familiarized with multi-range support added in pg14 but if that makes things easier I can upgrade...
CodePudding user response:
With PostgreSQL v14 and your data model, that could be as simple as
SELECT r.id,
/* convert the reservation to a multirange */
tsmultirange(r.during)
-
/* aggregate the appointments to a multirange */
range_agg(a.during)
AS free_slots
FROM reservation AS r
JOIN appointment AS a ON a.on_reservation = r.id
GROUP BY r.id, r.during;