Перекрывающиеся пробелы и острова в настройках школьных каникул - PullRequest
3 голосов
/ 08 февраля 2020

Мне нужно работать с этой periods таблицей:

периодов

id  | starts_on  |  ends_on   
----+------------+------------
678 | 2019-12-21 | 2019-12-22
534 | 2019-12-23 | 2020-01-04
679 | 2019-12-28 | 2019-12-29
  9 | 2020-01-01 | 2020-01-01
776 | 2020-01-04 | 2020-01-05
  7 | 2020-01-06 | 2020-01-06
777 | 2020-01-11 | 2020-01-12

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

С помощью Найти строки с диапазонами дат закрытия и накапливать их продолжительности и Пробелы и острова для школьных каникул в стране с федеральными землями Я закончил с этим запросом:

SELECT p.id, p.starts_on, p.ends_on, grp,
      (Max(ends_on) OVER (PARTITION BY grp) - Min(starts_on) OVER (PARTITION BY grp) 
      ) + 1 AS duration, Array_agg(p.id) OVER (PARTITION BY grp) 
FROM (SELECT p.*,
            Count(*) FILTER (WHERE prev_eo < starts_on - INTERVAL '1 day') OVER
                (PARTITION BY 1 
                  ORDER BY starts_on
                ) AS grp 
      FROM (SELECT p.*,
                  lag(ends_on) OVER (PARTITION BY 1 ORDER BY starts_on) AS prev_eo 
            FROM (SELECT p.id, p.starts_on, p.ends_on FROM periods p
            WHERE starts_on > '2019-12-15' AND
                  starts_on < '2020-01-15' ) p 
          ) p 
  ) p;

Что я получу

Это приводит в

id  | starts_on  |  ends_on   | grp | duration |   array_agg   
----+------------+------------+-----+----------+---------------
678 | 2019-12-21 | 2019-12-22 |   0 |       15 | {678,534,679}
534 | 2019-12-23 | 2020-01-04 |   0 |       15 | {678,534,679}
679 | 2019-12-28 | 2019-12-29 |   0 |       15 | {678,534,679}
  9 | 2020-01-01 | 2020-01-01 |   1 |        1 | {9}
776 | 2020-01-04 | 2020-01-05 |   2 |        3 | {776,7}
  7 | 2020-01-06 | 2020-01-06 |   2 |        3 | {776,7}
777 | 2020-01-11 | 2020-01-12 |   3 |        2 | {777}

Первые три строки: grp 0 (идентификаторы 678, 534 и 679).

Что я хочу

Но идентификаторы 9, 776 и 7 тоже должны принадлежать этому grp. К сожалению, они перекрываются. Можно ли получить результат, который как-то так (мне безразличен порядок)?

id  | starts_on  |  ends_on   | grp | duration |   array_agg   
----+------------+------------+-----+----------+---------------
678 | 2019-12-21 | 2019-12-22 |   0 |       17 | {678,534,679,9,776,7}
534 | 2019-12-23 | 2020-01-04 |   0 |       17 | {678,534,679,9,776,7}
679 | 2019-12-28 | 2019-12-29 |   0 |       17 | {678,534,679,9,776,7}
  9 | 2020-01-01 | 2020-01-01 |   0 |       17 | {678,534,679,9,776,7}
776 | 2020-01-04 | 2020-01-05 |   0 |       17 | {678,534,679,9,776,7}
  7 | 2020-01-06 | 2020-01-06 |   0 |       17 | {678,534,679,9,776,7}
777 | 2020-01-11 | 2020-01-12 |   1 |        2 | {777}

Я хочу знать, как долго длится весь остров (группа 0) и какие идентификаторы периода, которые он содержит.

Песочница: https://rextester.com/SHVL41709

1 Ответ

1 голос
/ 08 февраля 2020

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

К счастью, вы можете использовать совокупный max() для этой цели:

SELECT p.id, p.starts_on, p.ends_on, grp,
      (Max(ends_on) OVER (PARTITION BY grp) - Min(starts_on) OVER (PARTITION BY grp) 
      ) + 1 AS duration, Array_agg(p.id) OVER (PARTITION BY grp) 
FROM (SELECT p.*,
            Count(*) FILTER (WHERE prev_eo < starts_on - INTERVAL '1 day') OVER
                (PARTITION BY 1 
                  ORDER BY starts_on
                ) AS grp 
      FROM (SELECT p.*,
                  MAX(ends_on) OVER (ORDER BY starts_on ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS prev_eo 
            FROM (SELECT p.id, p.starts_on, p.ends_on 
                  FROM periods p
                  WHERE starts_on > '2019-12-15' AND
                        starts_on < '2020-01-15'
                 ) p 
          ) p 
  ) p;

Я не уверен, что такое PARTITION BY 1 предполагается, что он делает, но я не включил его.

Здесь - тестер.

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

...