Разрывы и острова - получите список дат безработных за диапазон дат с Postgresl - PullRequest
1 голос
/ 08 июля 2019

У меня есть таблица с названием Position, в этой таблице у меня есть следующие даты, включающие (гггг-мм-дд), ниже приведено упрощенное представление дат занятости

id, person_id, start_date, end_date  , title
1 , 1        , 2001-12-01, 2002-01-31, 'admin'
2 , 1        , 2002-02-11, 2002-03-31, 'admin'
3 , 1        , 2002-02-15, 2002-05-31, 'sales'
4 , 1        , 2002-06-15, 2002-12-31, 'ops'

Я хотел бы иметь возможность рассчитать разрывы в занятости, предполагая, что некоторые даты перекрываются, чтобы получить следующий результат для человека с id = 1

person_id, start_date, end_date  , last_position_id, gap_in_days
1        , 2002-02-01, 2002-02-10, 1               , 10
1        , 2002-06-01, 2002-06-14, 3               , 14

Я посмотрел на многочисленные решения, СОЮЗЫ, Материализованные представления, таблицы с сгенерированными календарными диапазонами дат и т. Д. Я действительно не уверен, каков наилучший способ сделать это. Есть ли один запрос, где я могу это сделать?

Ответы [ 2 ]

1 голос
/ 09 июля 2019

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

Вам просто нужна lead() функция окна . При этом вы можете получить значение (start_date в данном случае) для текущей строки.

SELECT
    person_id,
    end_date + 1 AS start_date,
    lead - 1 AS end_date,
    id AS last_position_id,
    lead - (end_date + 1) AS gap_in_days
FROM (
    SELECT 
        *,
        lead(start_date) OVER (PARTITION BY person_id ORDER BY start_date)
    FROM
        positions
) s
WHERE lead - (end_date + 1) > 0

После получения следующего start_date вы можете сравнить его с текущим end_date. Если они отличаются, у вас есть пробел. Эти положительные значения могут быть отфильтрованы в предложении WHERE.

(если 2 позиции перекрываются, разница отрицательна. Поэтому ее можно игнорировать.)

1 голос
/ 08 июля 2019
  • сначала нужно выяснить, какие даты перекрываются Определить, перекрываются ли два диапазона дат
  • , затем объединить эти диапазоны в один и сохранить последний идентификатор
  • окончательно вычислить диапазоны дней между одним end_date и следующим start_date - 1

SQL DEMO

with find_overlap as (
  SELECT t1."id" as t1_id, t1."person_id", t1."start_date", t1."end_date",
         t2."id" as t2_id, t2."start_date" as t2_start_date, t2."end_date" as t2_end_date
  FROM Table1 t1
  LEFT JOIN Table1 t2
    ON t1."person_id" = t2."person_id"
   AND t1."start_date" <= t2."end_date"
   AND t1."end_date"   >= t2."start_date"
   AND t1.id < t2.id
), merge_overlap as (
  SELECT 
         person_id,
         start_date,
         COALESCE(t2_end_date, end_date) as  end_date,
         COALESCE(t2_id, t1_id) as last_position_id
  FROM find_overlap
  WHERE t1_id NOT IN (SELECT t2_id FROM find_overlap WHERE t2_ID IS NOT NULL)
), cte as (
  SELECT *, 
         LEAD(start_date) OVER (partition by person_id order by start_date) next_start
  FROM merge_overlap 
) 
SELECT *, 
       DATE_PART('day', 
                  (next_start::timestamp - INTERVAL '1 DAY') - end_date::timestamp
                ) as days 
FROM cte
WHERE next_start IS NOT NULL 

ВЫХОД

| person_id | start_date |   end_date | last_position_id | next_start | days |
|-----------|------------|------------|------------------|------------|------|
|         1 | 2001-12-01 | 2002-01-31 |                1 | 2002-02-11 |   10 |
|         1 | 2002-02-11 | 2002-05-31 |                3 | 2002-06-15 |   14 |
...