postgresql список временных интервалов с понедельника | 09:00:00 | 11:00:00 - PullRequest
0 голосов
/ 02 февраля 2020

Я строю систему бронирования, в которой пользователь будет устанавливать свою доступность, например: я доступен по понедельникам с 9 до 11, вторникам с 9 до 17 и т. Д., И мне нужно сгенерировать список временных интервалов за 15 минут до их доступности. .

У меня есть следующая таблица (но я могу ее изменить):

availabilities(day_of_week text, start_time: time, end_time: time)

, которая возвращает записи типа:

‘Monday’ | 09:00:00 | 11:00:00
‘Monday’ | 13:00:00 | 17:00:00
‘Tuesday’ | 08:00:00 | 17:00:00

Итак, я пытаюсь построить хранимую процедуру для генерации списка временных интервалов, пока у меня есть это:

create or replace function timeslots ()
return setof timeslots as $$
  declare
    rec record;

  begin
    for rec in select * from availabilities loop
      /*
        convert 'Monday' | 09:00:00 | 11:00:00 into:
        2020-02-03 09:00:00
        2020-02-03 09:15:00
        2020-02-03 09:30:00
        2020-02-03 09:45:00
        2020-02-03 10:00:00
        and so on...
      */
      return next
    end loop
$$ language plpgsql stable;

Я возвращаю setof вместо таблицы, поскольку я использую Hasura и его необходимо вернуть setof, поэтому я просто создаю пустую таблицу.

Я думаю, что я на правильном пути, но в настоящее время застрял на:

  • как мне создать метку времени из «Понедельник» 09:00:00 на следующий понедельник, так как меня волнуют только временные интервалы с сегодняшнего дня?
  • как мне преобразовать 'Monday' | 09:00:00 | 11:00:00 в список временных интервалов с интервалом в 15 минут?

Ответы [ 2 ]

0 голосов
/ 03 февраля 2020

Следующее использует большинство методов, упомянутых @Brits. Они предоставляют очень хорошую информацию, поэтому я не буду повторяться, но предлагаю вам просмотреть ее (и ссылки). Однако я придерживаюсь немного другого подхода. Сначала пара таблиц меняется. Я использую день ISO недели 1-7 (понедельник-воскресенье), а не название дня. Название дня легко вывести на свидание позже. Также я использую интервал вместо времени для времени начала и окончания. (Тип данных времени работает для большинства сценариев ios, но есть и другой (подробнее позже). Одна вещь, которую ваше описание не проясняет, это то, включено ли время окончания в доступное время или нет. Если включен последний интервал будет 11: 00-11: 15. Если исключен, последний интервал 10: 45-11: 00. Я предполагал исключить это. В окончательных результатах время окончания следует понимать как «до, но не включая».

-- setup 
create table availabilities (weekday integer, start_time interval, end_time interval);


insert into availabilities (weekday , start_time , end_time )
   select wkday
        , start_time
        , end_time 
     from (select * 
             from (values (1, '09:00'::interval, '11:00'::interval) 
                        , (1, '13:00'::interval, '17:00'::interval)
                        , (2, '08:00'::interval, '17:00'::interval) 
                        , (3, '08:30'::interval, '10:45'::interval)
                        , (4, '10:30'::interval, '12:45'::interval)                         
                  ) as v(wkday,start_time,end_time)
          ) r ;

select * from availabilities;

Запрос Он начинается с CTE (next_week), генерирует запись для каждого дня недели, начинающегося с понедельника, и соответствующий номер дня ISO для него. Основной запрос объединяет их с таблицей доступности, чтобы выбрать время для соответствующих дней. Наконец, этот результат перекрестно соединяется с сгенерированной временной меткой, чтобы получить 15-минутные интервалы.

-- Main 
with next_week (wkday,tm) as 
     (SELECT n+1, date_trunc('week', current_date) + interval '1 week' + n*interval '1 day' 
        from generate_series (0, 6) n
     )  
select to_char(gdtm,'Day'), gdtm start_time, gdtm+interval '15 min' end_time
  from ( select wkday, tm, start_time, end_time
           from next_week   nw
           join availabilities av 
             on (av.weekday = nw.wkday)  
       ) s 
  cross join lateral 
        generate_series(start_time+tm, end_time+tm- interval '1 sec', interval '15 min') gdtm ;

Пропадание Как уже упоминалось, есть один сценарий, когда тип данных времени не работает удовлетворительно, но вам может и не понадобиться. Что происходит, когда сменный работник говорит, что у них есть свободное время 23: 00-01: 30. Поверьте мне, когда сменный работник идет на работу в 22:00 пятницы, 01:30 по-прежнему в пятницу вечером, хотя календарь может не согласиться. (Я работал над этим сдвигом в течение многих лет.) Следующее использование интервальных обработчиков этой проблемы. Загрузка тех же данных, что и ранее, с добавлением для этого случая.

 insert into availabilities (weekday, start_time, end_time )
    select wkday
         , start_time
         , end_time + case when end_time < start_time
                           then interval '1 day'
                           else interval '0 day'
                      end 
      from (select * 
              from (values (1, '09:00'::interval, '11:00'::interval) 
                         , (1, '13:00'::interval, '17:00'::interval)
                         , (2, '08:00'::interval, '17:00'::interval) 
                         , (3, '08:30'::interval, '10:45'::interval)
                         , (5, '23:30'::interval, '02:30'::interval)  -- Friday Night - Saturday Morning
                   ) as v(wkday,start_time,end_time)
           ) r
           ;           
select * from availabilities; 

Надеюсь, это поможет.

0 голосов
/ 02 февраля 2020

как мне создать метку времени с понедельника 09:00:00 на следующий понедельник, так как я забочусь только о временных интервалах с сегодняшнего дня?

Вы можете использовать date_trunc для этого (см. этот вопрос для получения дополнительной информации):

SELECT date_trunc('week', current_date) + interval '1 week';

Из документов re week:

Номер недельной нумерации ISO 8601 года. По определению, недели ISO начинаются по понедельникам

Поэтому, принимая это значение и добавляя неделю, вы получите следующий понедельник (вам может потребоваться изменить это поведение в зависимости от того, что вы хотите сделать, если сегодня понедельник!).

как мне конвертировать 'понедельник' | 09:00:00 | 11:00:00 в список временных интервалов с интервалом в 15 минут?

Это маленький обманщик; generate_series даст вам временные интервалы, но хитрость заключается в получении его в результирующий набор. Следующее должно выполнить эту работу (я включил ваши примеры данных; измените бит values для ссылки на вашу таблицу) - dbfiddle :

with avail_times as (
select
    date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + start_time as start_time,
    date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + end_time as end_time
from
    (
values 
('Monday','09:00:00'::time,'11:00:00'::time),
('Monday','13:00:00'::time,'17:00:00'::time),
('Tuesday','08:00:00'::time,'17:00:00'::time)
) as availabilities (day_of_week,
    start_time,
    end_time) )
select
    g.ts
from
    (
    select
        start_time,
        end_time
    from
        avail_times) avail,
    generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts);

Несколько примечаний:

  • CTE avail_times используется для упрощения вещей; он генерирует два столбца (start_time и end_time), которые являются полными временными метками (включая дату). В этом примере первая строка «2020-02-03 09:00:00, 2020-02-03 11:00:00» (я запускаю это на 2020-02-02, поэтому 2020-02-03 следующий Понедельник).
  • То, как я конвертирую 'понедельник' и c в день недели, - это нечто вроде хака (и я не удосужился сделать всю неделю); возможно, есть лучший способ, но сохранение дня недели в виде целого числа сделает это проще.
  • Я вычитаю 1 мс из времени окончания, потому что я предполагаю, что вы не хотите этого в наборе результатов.
  • Основной запрос использует LATERAL Subquery . См. этот вопрос для получения дополнительной информации.

Дополнительный вопрос

как настроить это, чтобы я мог передать дату начала и окончания, чтобы я мог получить временные интервалы для определенного периода

Вы можете сделать что-то вроде следующего (просто настройте dates CTE, чтобы возвращать те дни, которые вы хотите включить; вы можете преобразовать в функцию или просто передать даты в качестве параметров).

Обратите внимание, что, как упоминает @Belayer, мое оригинальное решение не учитывает сдвиги в течение полуночи, так что это также относится и к.

with dates as (
select
    day
from
    generate_series('2020-02-20'::date, '2020-03-10'::date, '1 day') as day ),
availabilities as (
select
    *
from
(
    values (1,'09:00:00'::time,'11:00:00'::time),
    (1,'13:00:00'::time,'17:00:00'::time),
    (2,'08:00:00'::time,'17:00:00'::time),
    (3,'23:00:00'::time,'01:00:00'::time) 
) as availabilities 
    (day_of_week,   -- 1 = monday
     start_time,
     end_time) ) ,
avail_times as (
select
    d.day + start_time as start_time,
    case
        end_time > start_time
        when true then d.day
        else d.day + interval '1 day' end + end_time as end_time
    from
        availabilities a
    inner join dates d on extract(ISODOW from d.day) = a.day_of_week )
select
    g.ts
from
    (
    select
        start_time,
        end_time
    from
        avail_times) avail,
    generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts)
order by
    g.ts;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...