генерировать серии, используя время перерыва - PullRequest
0 голосов
/ 26 октября 2018

У меня есть таблица, в которой хранятся часы открытия и закрытия часов

    CREATE TABLE public.open_hours
(
  id bigint NOT NULL,
  open_hour character varying(255),
  end_hour character varying(255),
  day character varying(255),
  CONSTRAINT pk_open_hour_id PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE public.open_hours
  OWNER TO postgres;

У меня есть еще один стол, который сотре

CREATE TABLE public.break_hours
(
id bigint ,
start_time character varying(255),
end_time character varying(255),
open_hour_id bigint ,
CONSTRAINT break_hours_pkey PRIMARY KEY (id),
 CONSTRAINT fkinhl5x01pnn54nv15ol5ntxr5 FOREIGN KEY (open_hour_id )
  REFERENCES public.open_hours(id) MATCH SIMPLE
  ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.break_hours
OWNER TO postgres;

Мне нужно сгенерировать временной ряд с 30-минутным интервалом на основе времени перерыва.

Например: если мои открытые часы - 08:00, а конечный час - 18:00, а мой перерыв - с 11:00 до 11:30, а другой перерыв - с 15:00 до 15:15. затем мне нужно создать серию с 08:00 до 11:00 и с 11:30 до 15:00 и с 03:15 до 18:00.

пример данных

open_hours
-----------
id              open_hours                  end_hour    day
 1              08:00 AM                    06:00 PM    Monday

break_hours


id        start_time   end_time   open_hour_id
 1        11:00 AM     11:30 AM    1
 2        03:00 PM     03:15 PM    1

Sample out put
--------------
08:00 AM
08:30 AM
09:00 AM
09:30 AM
10:00 AM
10:30 AM
11:30 AM
12:00 PM
12:30 PM
01:00 PM
01:30 PM
02:PM PM
02:30 PM
03:15 PM
03:45 PM
04:15 PM
04:45 PM
05:15 PM

Query used for generating series between open hours is
SELECT DISTINCT gs AS start_time,gs + interval '30min' as end_time 
                     FROM   generate_series( timestamp '2018-11-09 08:00 AM', timestamp '2018-11-09 06:00 PM', interval '30min' )gs 
                     ORDER BY start_time

1 Ответ

0 голосов
/ 26 октября 2018

Кажется, что моделирование вашей таблицы должно быть очищено.Например, вы должны хранить время не как текстовые типы, а как 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

Основная идея остается такой же, как в примере, только один день.Разница в том, что мы должны учитывать разные типы дней.Я расширил приведенный выше пример и добавил второй день с различными часами работы и перерыва.

Изменения:

  1. Функция окна в CTE получила PARTITION BY часть.Это гарантирует, что смещены только start_time, которые содержат в тот же день.
  2. LIMIT 1 больше не будет работать, поскольку ограничивает всю таблицу одной строкой.Это значение было изменено на DISTINCT ON (day_id), что ограничивает таблицу первой строкой каждого дня.
...