Извлечь количество дней в неделю из нескольких диапазонов дат - PullRequest
2 голосов
/ 28 марта 2019

У меня есть таблица trips в PostgreSQL 10.5:

id  start_date    end_date
----------------------------
1   02/01/2019    02/03/2019
2   02/02/2019    02/03/2019
3   02/06/2019    02/07/2019
4   02/06/2019    02/14/2019
5   02/06/2019    02/06/2019

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

week_of    days_utilized
------------------------
01/28/19    5
02/04/19    8
02/11/19    4

Для календаря:

Monday 01/28/19 - Sunday 02/03/19
Monday 02/04/19 - Sunday 02/10/19
Monday 02/11/19 - Sunday 02/17/19

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

Ответы [ 2 ]

3 голосов
/ 29 марта 2019

Вы, кажется, хотите generate_series() и join и group by. Для подсчета покрытой недели:

select gs.wk, count(t.id) as num_trips
from generate_series('2019-01-28'::date, '2019-02-11'::date, interval '1 week') gs(wk) left join
     trips t
     on gs.wk <= t.end_date and
        gs.wk + interval '6 day' >= t.start_date
group by gs.wk
order by gs.wk;

EDIT:

Я вижу, вы хотите, чтобы дни были освещены. Это немного больше работы в агрегации:

select gs.wk, count(t.id) as num_trips,
       sum( 1 +
            extract(day from (least(gs.wk + interval '6 day', t.end_date) - greatest(gs.wk, t.start_date)))
          ) as days_utilized
from generate_series('2019-01-28'::date, '2019-02-11'::date, interval '1 week') gs(wk) left join
     trips t
     on gs.wk <= t.end_date and
        gs.wk + interval '6 day' >= t.start_date
group by gs.wk
order by gs.wk;

Примечание: это не возвращает именно те результаты, которые у вас есть. Я думаю, что это правильно.

0 голосов
/ 29 марта 2019

Я бы рассмотрел типы диапазонов для этого. Делает вычисления проще и понятнее с операторами диапазона - я использую оверлаг && и пересечение * ниже. И мы можем использовать функционал GiST или индекс SP-GiST для быстрого выполнения запросов - если таблица большая. Как:

CREATE INDEX trip_range_idx ON trip
USING gist (daterange(start_date, end_date, '[]'));

Тогда ваш запрос может использовать этот индекс:

SELECT week
     , count(overlap)                       AS ct_trips
     , sum(upper(overlap) - lower(overlap)) AS days_utilized
FROM  (
   SELECT week, trip * week AS overlap
   FROM  (
      SELECT daterange(mon::date, mon::date + 7) AS week
      FROM   generate_series(timestamp '2019-01-28'
                           , timestamp '2019-02-11'
                           , interval  '1 week') mon
      ) w
   LEFT   JOIN (SELECT daterange(start_date, end_date, '[]') FROM trip) t(trip) ON trip && week
   ) sub
GROUP  BY 1
ORDER  BY 1;

дБ <> скрипка здесь

Обратите внимание, что по умолчанию date_range состоит из включительно нижней и исключительной верхней границы. Ваши диапазоны включают верхнюю и нижнюю границы, поэтому создайте daterange с: daterange(start_date, end_date, '[]'). Функция upper() по-прежнему возвращает исключительную верхнюю границу. Следовательно, выражение upper(overlap) - lower(overlap) правильно подсчитывает дни.

Существует причина, по которой я использую generate_series() с timestamp вводом:

Связанный:

или , если вы не хотите использовать типы диапазонов, рассмотрите оператор OVERLAPS:

...