Найти строки с отложенными диапазонами дат и накапливать их длительности - PullRequest
0 голосов
/ 07 февраля 2020

Моя PostgreSQL база данных хранит школьные каникулы, публичные c праздники и даты выходных для родителей, чтобы планировать свой отпуск. Часто школьные каникулы откладываются в выходные или праздничные дни. Я хочу показать общее количество не школьных дней для школьных каникул. Это должно включать любые перенесенные выходные или праздничные дни c.

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

местоположений

SELECT id, name, is_federal_state 
FROM locations 
WHERE is_federal_state = true;
| id | name              | is_federal_state |
|----|-------------------|------------------|
| 2  | Baden-Württemberg | true             |
| 3  | Bayern            | true             |

holiday_or_vacation_types

SELECT id, name FROM holiday_or_vacation_types;
| id | name                  |
|----|-----------------------|
| 1  | Herbst                |
| 8  | Wochenende            |

«Хербст» по-немецки означает «осень», а «Вочененде» по-немецки означает «выходные».

периоды

SELECT id, starts_on, ends_on, holiday_or_vacation_type_id 
FROM periods 
WHERE location_id = 2 
ORDER BY starts_on;
| id  | starts_on    | ends_on      | holiday_or_vacation_type_id |
|-----|--------------|--------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 8                           |

Задание

Я хочу select все periods, где location_id равно 2. И я хочу вычислить продолжительность каждого периода в днях. Это можно сделать с помощью этого SQL запроса:

SELECT id, starts_on, ends_on, 
       (ends_on - starts_on + 1) AS duration, 
       holiday_or_vacation_type_id 
FROM periods
| id  | starts_on    | ends_on      | duration | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        | 8                           |

Любой человек, смотрящий на календарь, увидит, что идентификаторы 670 (выходные), 532 (осенние каникулы) и 533 ( осенние каникулы) откладываются. Таким образом, они в сумме составляют 6-дневный отпуск. Пока я делаю это с помощью программы, которая это вычисляет. Но это занимает довольно много ресурсов (фактическая таблица содержит около 500 000 элементов).

Задача 1

Какой запрос SQL приведет к следующему выводу (добавляет real_duration столбец)? Возможно ли это даже с SQL?

| id  | starts_on    | ends_on      | duration | real_duration | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|---------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 6             | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 6             | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 6             | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        | 2             | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        | 2             | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        | 2             | 8                           |

Задача 2

Можно ли перечислить периоды закрытия в поле part_of_range? Это будет результат. Можно ли это сделать с помощью SQL?

| id  | starts_on    | ends_on      | duration | part_of_range | holiday_or_vacation_type_id |
|-----|--------------|--------------|----------|---------------|-----------------------------|
| 670 | "2019-10-26" | "2019-10-27" | 2        | 670,532,533   | 8                           |
| 532 | "2019-10-28" | "2019-10-30" | 3        | 670,532,533   | 1                           |
| 533 | "2019-10-31" | "2019-10-31" | 1        | 670,532,533   | 1                           |
| 671 | "2019-11-02" | "2019-11-03" | 2        |               | 8                           |
| 672 | "2019-11-09" | "2019-11-10" | 2        |               | 8                           |
| 673 | "2019-11-16" | "2019-11-17" | 2        |               | 8                           |

1 Ответ

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

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

Последняя операция - это некоторое агрегирование (с использованием оконных функций):

SELECT p.*, 
      (Max(ends_on) OVER (PARTITION BY location_id, grp) - Min(starts_on) OVER (PARTITION BY location_id, grp) ) + 1 AS duration,
      Array_agg(p.id) OVER (PARTITION BY location_id) 
FROM (SELECT p.*, 
             Count(*) FILTER (WHERE prev_eo < starts_on - INTERVAL '1 day') OVER (PARTITION BY location_id ORDER BY starts_on) AS grp
      FROM (SELECT id, starts_on, ends_on, location_id, holiday_or_vacation_type_id, 
                   lag(ends_on) OVER (PARTITION BY location_id ORDER BY (starts_on)) AS prev_eo
            FROM periods 
           ) p
     ) p;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...