Как я могу сгенерировать ряд диапазонов дат, которые находятся между датой начала и датой окончания?

#postgresql

#postgresql

Вопрос:

Я хотел бы сгенерировать массив всех временных диапазонов, которые находятся в пределах 4 часов вечера -9 часов вечера между начальной и конечной датой

 start_date          | end_date
----------------------------------------
2020-11-01 16:30:00 | 2020-11-03 18:30:00
 

Запрос должен быть способен превратить приведенную выше таблицу в:

 row | start_date          | end_date
------------------------------------------------
1   | 2020-11-01 16:30:00 | 2020-11-01 21:00:00
------------------------------------------------
2   | 2020-11-02 16:00:00 | 2020-11-02 21:00:00
------------------------------------------------
3   | 2020-11-03 16:00:00 | 2020-11-03 18:30:00
 

Может ли кто-нибудь указать мне правильное направление в том, как подойти к этому?

Комментарии:

1. Что, если start_date равен 2020-11-01 15:00:00 ?

Ответ №1:

Я бы сделал это так:

 with input as (
  select '2020-11-01 16:30:00'::timestamptz as start_date,
         '2020-11-03 18:30:00'::timestamptz as end_date
)
select row_number() over (order by ddate) as row,
       case 
         when     start_date::date = ddate  
              and start_date > ddate   interval '16 hours' 
           then start_date
         else ddate   interval '16 hours'
       end as start_date,
       case
         when     end_date::date = ddate
              and end_date < ddate   interval '21 hours'
           then end_date
         else ddate   interval '21 hours'
       end as end_date 
  from input
       cross join lateral 
         generate_series(
           case 
             when start_date::time > '21:00' then start_date::date   interval '1 day'
             else start_date::date
           end, 
           case 
             when end_date::time < '16:00' then end_date::date - interval '1 day'
             else end_date::date
           end, 
           interval '1 day') as gs(ddate)
;
┌─────┬────────────────────────┬────────────────────────┐
│ row │       start_date       │        end_date        │
├─────┼────────────────────────┼────────────────────────┤
│   12020-11-01 16:30:00-052020-11-01 21:00:00-05 │
│   22020-11-02 16:00:00-052020-11-02 21:00:00-05 │
│   32020-11-03 16:00:00-052020-11-03 18:30:00-05 │
└─────┴────────────────────────┴────────────────────────┘
(3 rows)

 

Комментарии:

1. Я бы просто генерировал ряды в течение нескольких часов, а не дней. Таким образом, вы можете фильтровать их напрямую. Смотрите мое решение.

Ответ №2:

Когда вы используете generate_series с часами вместо дней, как в решении @MikeOrganek, вы можете фильтровать их напрямую. Начальные и конечные часы должны быть «полными часами», что может быть достигнуто с помощью date_trunc() . Поскольку date_trunc сокращаются последние минуты конечной даты, имеет смысл создать ряд на час больше; вот почему в примере добавлен час.

Наконец GROUP BY , укажите дату и укажите MIN MAX время. С least() помощью и greatest() вы можете настроить граничные случаи.

пошаговая демонстрация: db<>fiddle

 SELECT
    GREATEST(MIN(gs), start_date) as start_time,
    LEAST(MAX(gs), end_date) as end_time
FROM
    t,
    generate_series(
        date_trunc('hour', start_date), 
        date_trunc('hour', end_date)   interval '1 hour',
        interval '1 hour'
    ) as gs
WHERE gs::time >= '16:00:00' and gs::time <= '21:00:00'
GROUP BY start_date, end_date, gs::date
ORDER BY gs::date
 

Ответ №3:

Другим решением является использование generate_series() с интервалом в один день и построение временной части в зависимости от значения времени в начальной / конечной дате для первого и последнего дня.

 with from_to (start_date, end_date) as (
 values (timestamp '2020-11-01 16:30:00', timestamp '2020-11-03 18:30:00')
)
select g.nr as row, 
       g.d::date   case 
                      when g.d::date = start_date::date and start_date::time > time '16:00' then start_date::time
                      else time '16:00'
                   end as start_date,
       g.d::date   case 
                     when g.d::date = end_date::date and end_date::time < time '21:00' then end_date::time
                     else time '21:00'
                  end as end_date
from from_to 
  cross join generate_series(start_date, end_date, interval '1 day') with ordinality as g(d,nr)
order by g.nr;  
 

Онлайн-пример