Как найти первое свободное время в таблице бронирования в PostgreSql
Таблица бронирования содержит даты начала бронирования, часы начала и продолжительность. Начальный час увеличивается на полчаса в рабочие часы с 8:00 до 18:00 в рабочие дни. Продолжительность также на полчаса в день.
CREATE TABLE reservation (
startdate date not null, -- start date
starthour numeric(4,1) not null , -- start hour 8 8.5 9 9.5 .. 16.5 17 17.5
duration Numeric(3,1) not null, -- duration by hours 0.5 1 1.5 .. 9 9.5 10
primary key (startdate, starthour)
);
Структура таблицы может быть изменена при необходимости.
Как найти первые свободные полчаса в таблице, которая не зарезервирована? Если таблица содержит
startdate starthour duration
14 9 1 -- ends at 9:59
14 10 1.5 -- ends at 11:29, e.q there is 30 minute gap before next
14 12 2
14 16 2.5
Результат должен быть:
starthour duration
11.5 0.5
Вероятно, оконная функция PostgreSql 9.2 должна использоваться для поиска первой строки, чей начальный час больше, чем предыдущая строка, начальный час + продолжительность
Как написать оператор выбора, который возвращает эту информацию?
2 ответа
Postgres 9.2 имеет тип диапазона, и я бы рекомендовал использовать их.
create table reservation (reservation tsrange);
insert into reservation values
('[2012-11-14 09:00:00,2012-11-14 10:00:00)'),
('[2012-11-14 10:00:00,2012-11-14 11:30:00)'),
('[2012-11-14 12:00:00,2012-11-14 14:00:00)'),
('[2012-11-14 16:00:00,2012-11-14 18:30:00)');
ALTER TABLE reservation ADD EXCLUDE USING gist (reservation WITH &&);
"EXCLUDE USING gist" создает индекс, который запрещает вставлять перекрывающиеся записи. Вы можете использовать следующий запрос для поиска пробелов (вариант запроса vyegorov):
with gaps as (
select
upper(reservation) as start,
lead(lower(reservation),1,upper(reservation)) over (ORDER BY reservation) - upper(reservation) as gap
from (
select *
from reservation
union all values
('[2012-11-14 00:00:00, 2012-11-14 08:00:00)'::tsrange),
('[2012-11-14 18:00:00, 2012-11-15 00:00:00)'::tsrange)
) as x
)
select * from gaps where gap > '0'::interval;
'объединение всех значений' маскирует нерабочее время, поэтому вы можете сделать заказ только с 8:00 до 18:00.
Вот результат:
start | gap
---------------------+----------
2012-11-14 08:00:00 | 01:00:00
2012-11-14 11:30:00 | 00:30:00
2012-11-14 14:00:00 | 02:00:00
Ссылки на документацию: - http://www.postgresql.org/docs/9.2/static/rangetypes.html "Типы диапазонов" - https://wiki.postgresql.org/images/7/73/Range-types-pgopen-2012.pdf
Возможно, не самый лучший запрос, но он делает то, что вы хотите:
WITH
times AS (
SELECT startdate sdate,
startdate + (floor(starthour)||'h '||
((starthour-floor(starthour))*60)||'min')::interval shour,
startdate + (floor(starthour)||'h '||
((starthour-floor(starthour))*60)||'min')::interval
+ (floor(duration)||'h '||
((duration-floor(duration))*60)||'min')::interval ehour
FROM reservation),
gaps AS (
SELECT sdate,shour,ehour,lead(shour,1,ehour)
OVER (PARTITION BY sdate ORDER BY shour) - ehour as gap
FROM times)
SELECT * FROM gaps WHERE gap > '0'::interval;
Некоторые заметки:
- Будет лучше не отделять время и данные мероприятия. Если вам нужно, то используйте стандартные типы;
- Если невозможно использовать стандартные типы, создайте функцию для преобразования
numeric
часов вtime
формат.