Использование SQL для определения периодов времени с датами начала и окончания - PullRequest
0 голосов
/ 21 марта 2019

Я готовлю некоторые данные для исследовательского проекта, и у меня возникли проблемы со следующей задачей.Если возможно, я хотел бы сделать все с SQL или PL SQL (хотя я настоящий новичок с PL).

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

+-------+-----------+--------------+--------------+-----------+
| Row # | Person_ID |     Code     |     Date     | Period_ID |
+-------+-----------+--------------+--------------+-----------+
|     1 |         1 | Start_period | Jan 1st      |         1 |
|     2 |         1 | End_period   | Jan 15th     |         1 |
|     3 |         1 | Random_code1 | Feb 15th     |         1 |
|     4 |         1 | Random_code2 | Feb 28th     |         1 |
|     5 |         1 | End_period   | March 31st   |         1 |
|     6 |         1 | Start_period | May 31st     |         2 |
|     7 |         1 | End_period   | June 11th    |         2 |
|     8 |         1 | End_period   | October 28th |         2 |
+-------+-----------+--------------+--------------+-----------+

Столбцы и вызов:

  • Person_ID : все вышеприведенные данные относятся к одному лицу (эти данные находятся на уровне транзакции)).
  • Код : Этот код может быть начальным_периодом, конечным_периодом или любым произвольным кодом.Каждый код Start_period должен иметь соответствующий код End_period. Задача этой проблемы - определить все пары Start / End для создания столбца Period_ID. Один важный нюанс этой задачи: код End_period равен INVALID , если он находится в течение 28 дней.кода Start_period.Например, код End_period в строке 2 недействителен, потому что он 15 января, всего через 14 дней после 1 января.Вместо этого действительный код End_period находится в строке 5, поскольку это происходит более чем через 28 дней.
  • Дата : дата транзакции
  • Period_ID : Желаемая строка - этой информации в данный момент нет в таблице.

Ответы [ 3 ]

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

Вот ответ с использованием всегда интересного Match_Recognize.Обратите внимание, что вы не должны называть столбцы «Код» или «Дата», поскольку они являются зарезервированными ключевыми словами.

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

WITH test_vals AS (
    SELECT 1 as person_ID,'Start_period' as my_code,to_date('Jan 1','mon dd') as my_date FROM DUAL
    UNION ALL SELECT 1,'End_period',to_date('Jan 15','mon dd') FROM DUAL
    UNION ALL SELECT 1,'Random_code1',to_date('Feb 15','mon dd') FROM DUAL
    UNION ALL SELECT 1,'Random_code2',to_date('Feb 28','mon dd') FROM DUAL
    UNION ALL SELECT 1,'End_period',to_date('March 31','mon dd') FROM DUAL
    UNION ALL SELECT 1,'Start_period',to_date('May 31','mon dd') FROM DUAL
    UNION ALL SELECT 1,'End_period',to_date('June 11','mon dd') FROM DUAL
    UNION ALL SELECT 1,'End_period',to_date('October 28','mon dd') FROM DUAL
)

SELECT m.person_id,
       m.my_code,
       m.my_date,
       m.period_id
FROM test_vals t
match_recognize(
    PARTITION BY person_id
    ORDER BY my_date
    MEASURES
        match_number() AS period_id /* Return the match number as the period ID */
    ALL ROWS PER match
    pattern (
        start_code /* Match a single start code */
        (invalid_end_code | other_code)* /* Match zero or more invalid end codes or other codes */
        valid_end_code /* Match a single end code */
    )
    define
        start_code AS my_code = 'Start_period', /* Start codes are always valid */
        valid_end_code AS my_code = 'End_period' AND (my_date - FIRST(my_date)) > 28, /* End codes are only valid if they come more than 28 days after the start of the pattern match */
        invalid_end_code AS my_code = 'End_period' AND (my_date - FIRST(my_date)) <= 28,
        other_code AS my_code NOT IN ('Start_period', 'End_period')
) m
0 голосов
/ 21 марта 2019

Я бы использовал рекурсивный cte, как здесь:

with c(row_, code, date_, st_date, period, chg) as (
    select row_, code, date_, date_, 1, 0 from t where row_ = 1
    union all
    select t.row_, t.code, t.date_, 
           case when chg = 1 then t.date_ else st_date end, 
           case when chg = 1 then period + 1 else period end, 
           case when t.code = 'End_period' and t.date_ - c.st_date > 28 then 1 else 0 end
      from t join c on t.row_ = c.row_ + 1
    )
select row_, code, date_, period from c

Демоверсия dbfiddle

Логика заключается в использовании столбца chg, который управляет изменением периода. Chg устанавливается в 1, если код End period и дата больше, чем ранее запомненная дата начала. В следующем шаге период увеличивается, chg сбрасывается в ноль и устанавливается новая начальная дата.

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

Просто посчитайте количество начальных периодов в каждой строке:

select t.*,
       sum(case when code = 'Start_period' then 1 else 0 end) over (partition by person_id order by date) as period_id
from t;

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

...