Кажется, что моделирование вашей таблицы должно быть очищено.Например, вы должны хранить время не как текстовые типы, а как time without time zone
.
demo: db <> fiddle WITH hours AS (
SELECT
oh.open_hour + '1970-01-01'::date as open_hour,
oh.end_hour + '1970-01-01'::date as end_hour,
bh.start_time + '1970-01-01'::date as break_start,
bh.end_time + '1970-01-01'::date as break_end,
lead(start_time + '1970-01-01'::date) OVER (ORDER BY start_time) as next_start_time
FROM open_hours oh
LEFT JOIN break_hours bh
ON oh.id = bh.start_date
)
SELECT generate_series(open_hour, break_start, interval '30 minutes')::time as time_slot
FROM (
SELECT
open_hour, break_start
FROM hours
ORDER BY break_start
LIMIT 1
)s
UNION
SELECT
generate_series(break_end, next_start_time, interval '30 minutes')::time
FROM (
SELECT
break_end, next_start_time
FROM
hours
WHERE next_start_time IS NOT NULL
) s
UNION
SELECT generate_series(break_end, end_hour, interval '30 minutes')::time
FROM (
SELECT
break_end, end_hour
FROM hours
ORDER BY break_start DESC
LIMIT 1
) s
Объяснение :
WITH
предложение (CTE):
Объединение обеих таблиц.Я добавляю бессмысленную дату, потому что это приводит к timestamp
.Используемая позже функция generate_series
работает только для timestamp
s, а не для типа time
.Часть вырубается позже после генерации с использованием ::time
.
Результат CTE:
open_hour end_hour break_start break_end next_start_time
1970-01-01 08:00:00 1970-01-01 18:00:00 1970-01-01 09:30:00 1970-01-01 09:45:00 1970-01-01 11:00:00
1970-01-01 08:00:00 1970-01-01 18:00:00 1970-01-01 11:00:00 1970-01-01 11:30:00 1970-01-01 15:00:00
1970-01-01 08:00:00 1970-01-01 18:00:00 1970-01-01 15:00:00 1970-01-01 15:15:00 (NULL)
UNION
деталь:
Эта часть состоит из трех частей.Потому что мне нужно объединить временные ряды из обеих таблиц:
1. Принимая час открытия.Генерация временного ряда до начала первого перерыва.
Для этого мне нужен только первый ряд из CTE выше.Вот почему используется LIMIT 1
.
2. Для всех перерывов: создание временного ряда от окончания текущего перерыва до начала следующего перерыва.
CTE содержит оконную функцию lead()
, которая сдвигает start_time
следующей строки в текущую (взгляните на последний столбец результата CTE).Так что теперь я могу получить все времена перерыва, независимо от того, сколько их.В моем примере я добавил третий разрыв с 9:30
до 9:45
, чтобы продемонстрировать это.Таким образом, следующий временной ряд может быть сгенерирован из всех этих столбцов (с текущих break_end
до next_start_time
).Только последняя строка не содержит next_start_time
, поскольку ее нет.
3. Последний шаг: создание временного ряда от последнего перерыва, заканчивающегося до часа закрытия.
Это тихо похоже на (1).После итерации всех перерывов я должен добавить последний временной ряд от последнего перерыва к времени закрытия.Это может быть достигнуто либо фильтрацией строки без next_start_time
, либо сортировкой DESC
и использованием LIMIT 1
, как я сделал.
Более сложный случай с большим количеством типов дней:
demo: db <> fiddle
WITH hours AS (
SELECT
oh.id as day_id,
oh.open_hour + '1970-01-01'::date as open_hour,
oh.end_hour + '1970-01-01'::date as end_hour,
bh.start_time + '1970-01-01'::date as break_start,
bh.end_time + '1970-01-01'::date as break_end,
lead(start_time + '1970-01-01'::date) OVER (PARTITION BY oh.id ORDER BY start_time) as next_start_time
FROM open_hours oh
LEFT JOIN break_hours bh
ON oh.id = bh.start_date
)
SELECT day_id, generate_series(open_hour, break_start, interval '30 minutes')::time as time_slot
FROM (
SELECT DISTINCT ON (day_id)
day_id, open_hour, break_start
FROM hours
ORDER BY day_id, break_start
)s
UNION
SELECT
day_id, generate_series(break_end, next_start_time, interval '30 minutes')::time
FROM (
SELECT
day_id, break_end, next_start_time
FROM
hours
WHERE next_start_time IS NOT NULL
) s
UNION
SELECT day_id, generate_series(break_end, end_hour, interval '30 minutes')::time
FROM (
SELECT DISTINCT ON (day_id)
day_id, break_end, end_hour
FROM hours
ORDER BY day_id, break_start DESC
) s
ORDER BY day_id, time_slot
Основная идея остается такой же, как в примере, только один день.Разница в том, что мы должны учитывать разные типы дней.Я расширил приведенный выше пример и добавил второй день с различными часами работы и перерыва.
Изменения:
- Функция окна в CTE получила
PARTITION BY
часть.Это гарантирует, что смещены только start_time
, которые содержат в тот же день. LIMIT 1
больше не будет работать, поскольку ограничивает всю таблицу одной строкой.Это значение было изменено на DISTINCT ON (day_id)
, что ограничивает таблицу первой строкой каждого дня.