Вычисление / вывод первой начальной даты в непрерывных интервалах дат

#sql #postgresql #datetime #window-functions #gaps-and-islands

#sql #postgresql #дата-время #окно-функции #пробелы и острова

Вопрос:

Я использую Postgres и пытаюсь разобраться, как именно я мог бы получить первую начальную дату в непрерывных интервалах дат. Например :-

 ID | Start Date | End Date
==========================
1|2020-01-01|2020-01-31
1|2020-02-01|2020-03-31
1|2020-05-01|2020-06-30
1|2020-07-01|2020-07-31
1|2020-08-01|2020-08-31
  

Результат, который я ожидаю, это

 ID | Start Date | End Date | Continous Date
===========================================
1|2020-01-01|2020-01-31|2020-01-01
1|2020-02-01|2020-03-31|2020-01-01
1|2020-05-01|2020-06-30|2020-05-01
1|2020-07-01|2020-07-31|2020-05-01
1|2020-08-01|2020-08-31|2020-05-01
  

По сути, это должно дать мне самую первую начальную дату непрерывного диапазона дат.

Ценю ваши входные данные или указания о том, как я мог бы это сделать. К сожалению, CTE — это то, с чем я, возможно, не смогу согласиться.

Ответ №1:

Это проблема пробелов и островов, когда острова определяются как группа последовательных записей со смежными датами. Вам нужно начало каждого острова.

Вот подход, который используется lag() для извлечения «предыдущей» конечной даты, а затем создает группу с кумулятивным sum() значением, которое увеличивается при каждом разрыве.

 select t.*, 
    min(start_date) over(partition by id, grp order by start_date) continous_date
from (
    select t.*, 
        count(*) filter(where start_date is distinct from lag_end_date   interval '1 day') over(partiton by id order by start_date) grp
    from (
        select t.*, 
            lag(end_date) over(partition by id order by start_date) lag_end_date
        from mytable t
    ) t
) t
  

Ответ №2:

Это проблема пробелов и островов. В принципе, определите, где есть перекрытие, используя lag() или какую-либо другую функцию. Затем «острова» идентифицируются совокупной суммой неперекрывающихся. Я бы подошел к этому как:

 select t.*,
       min(start_date) over (partition by id, grp) as continuous_start_date
from (select t.*,
             count(*) filter (where prev_end_date is null or prev_end_date < start_date - interval '1 day') as grp
      from (select t.*,
                   max(end_date) over (partition by id order by start_date rows between unbounded preceding and 1 preceding) as prev_end_date
            from t
           ) t
     ) t;
  

Обратите внимание, что это обрабатывает перекрытия между соседними записями, а также начинается накануне.