2016-01-11 5 views
1

Скажем, у меня есть следующие таблицы базы данных:PL/SQL: Как преобразовать несколько строк с непрерывными временными рамками в одну строку, конвертируя весь временной интервал?

id  |  from  |  to 
1  | 01-JAN-2015 | 03-MAR-2015 
1  | 04-MAR-2015 | 31-AUG-2015 
1  | 01-SEP-2015 | 31-DEC-2015 
2  | 01-JAN-2015 | 30-JUN-2015 
2  | 01-NOV-2015 | 31-DEC-2015 

И я хочу подвести итог записи с тем же идентификатором, непрерывных во времени в один ряд, охватывающий полный кадр времени, следующим образом:

id  |  from  |  to 
1  | 01-JAN-2015 | 31-DEC-2015 
2  | 01-JAN-2015 | 30-JUN-2015 
2  | 01-NOV-2015 | 31-DEC-2015 

Таким образом, поскольку временные рамки являются последовательными и не имеют между ними пробелов, три строки для id 1 могут быть преобразованы в одну единственную строку с минимумом с даты и максимума на сегодняшний день. 2 строки для id 2 останутся такими же, как временные рамки не являются непрерывными.

Я думаю об этом, используя петлю через курсор, но я могу усложнять ситуацию. Любые лучшие идеи? возможно, только с SQL-запросами?

ответ

1

Вы можете сделать это с помощью иерархических запросов, что-то вроде этого: выполнение

select id, min(root_dt_from) dt_from, dt_to 
    from (select id, dt_from, dt_to, level, connect_by_isleaf, connect_by_root(dt_from) root_dt_from 
      from t 
      where connect_by_isleaf = 1 
     connect by prior id = id and prior (dt_to + 1) = dt_from 
     ) 
group by id, dt_to; 

Пример:

SQL> with t as (
    2 select 1 id, to_date('01-JAN-2015', 'DD-MON-YYYY') dt_from, to_date('03-MAR-2015', 'DD-MON-YYYY') dt_to from dual union all 
    3 select 1 id, to_date('04-MAR-2015', 'DD-MON-YYYY') dt_from, to_date('31-AUG-2015', 'DD-MON-YYYY') dt_to from dual union all 
    4 select 1 id, to_date('01-SEP-2015', 'DD-MON-YYYY') dt_from, to_date('31-DEC-2015', 'DD-MON-YYYY') dt_to from dual union all 
    5 select 2 id, to_date('01-JAN-2015', 'DD-MON-YYYY') dt_from, to_date('30-JUN-2015', 'DD-MON-YYYY') dt_to from dual union all 
    6 select 2 id, to_date('01-NOV-2015', 'DD-MON-YYYY') dt_from, to_date('31-DEC-2015', 'DD-MON-YYYY') dt_to from dual 
    7 ) -- end of sample data 
    8 select id, min(root_dt_from) dt_from, dt_to 
    9 from (select id, dt_from, dt_to, level, connect_by_isleaf, connect_by_root(dt_from) root_dt_from 
10   from t 
11   where connect_by_isleaf = 1 
12   connect by prior id = id and prior (dt_to + 1) = dt_from 
13  ) 
14 group by id, dt_to; 

     ID DT_FROM  DT_TO 
---------- ----------- ----------- 
     1 01-JAN-2015 31-DEC-2015 
     2 01-NOV-2015 31-DEC-2015 
     2 01-JAN-2015 30-JUN-2015 
+0

Вау !! ... отличное решение! ... Я бы не подумал иерархические псевдостолбцы могут быть полезными для этого сценария ... Спасибо. – Beto

0

Вы можете сделать это этапы с несколькими аналитическими и агрегатных функций:

with t1(id, from_dt, to_dt) as (
    select 1, to_date('01-JAN-2015', 'dd-mon-rrrr'), to_date('03-MAR-2015', 'dd-mon-rrrr') from dual union all 
    select 1, to_date('04-MAR-2015', 'dd-mon-rrrr'), to_date('31-AUG-2015', 'dd-mon-rrrr') from dual union all 
    select 1, to_date('01-SEP-2015', 'dd-mon-rrrr'), to_date('31-DEC-2015', 'dd-mon-rrrr') from dual union all 
    select 2, to_date('01-JAN-2015', 'dd-mon-rrrr'), to_date('30-JUN-2015', 'dd-mon-rrrr') from dual union all 
    select 2, to_date('01-NOV-2015', 'dd-mon-rrrr'), to_date('31-DEC-2015', 'dd-mon-rrrr') from dual 
), t2 as (
    select id 
     , from_dt 
     , to_dt 
     , from_dt-lag(to_dt,1,from_dt-1) over (partition by id order by to_dt) dst 
     , row_number() over (partition by id order by to_dt) rn 
    from t1 
), t3 as (
    select id 
     , from_dt 
     , to_dt 
     , sum(dst) over (partition by id order by rn) - rn grp 
    from t2 
) 
select id 
    , min(from_dt) from_dt 
    , max(to_dt) to_dt 
    from t3 
group by id, grp; 

Первый этап T1 просто воссоздавать данные. В T2 я вычитаю лагом to_dt from_dt, чтобы найти расстояние (dst) между последовательными записями и генерировать row_number для каждой записи (rn). В T3 я вычитаю rn из текущей суммы dst для генерации группы id (grp). Наконец, на выходном этапе я беру min и max from_dt и to_dt, соответственно группируя по столбцам ID и grp.

0

Вы можете попробовать здесь некоторые аналитические функции, которые могут реально упростить сценарий. Надеюсь, что этот ниже фрагмент поможет. Сообщите мне о любых проблемах .

SELECT B.ID, 
    MIN(B.FRM_DT) FRM_DT, 
    MAX(B.TO_DT) TO_DT 
FROM 
    (SELECT A.ID, 
    A.FRM_DT, 
    A.TO_DT, 
    NVL(LAG(A.TO_DT+1) OVER(PARTITION BY A.ID ORDER BY A.TO_DT),A.FRM_DT) nxt_dt, 
    CASE 
     WHEN NULLIF(A.FRM_DT,NVL(LAG(A.TO_DT+1) OVER(PARTITION BY A.ID ORDER BY A.TO_DT),A.FRM_DT)) IS NULL 
     THEN 'True' 
     ELSE 'False' 
    END COND 
    FROM 
    (SELECT 1 AS ID, 
     TO_DATE('01/01/2015') FRM_DT, 
     TO_DATE('03/03/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 1 AS ID, 
     TO_DATE('03/04/2015') FRM_DT, 
     TO_DATE('07/31/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 1 AS ID, 
     TO_DATE('08/01/2015') FRM_DT, 
     TO_DATE('12/31/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 2 AS ID, 
     TO_DATE('01/01/2015') FRM_DT, 
     TO_DATE('06/30/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 2 AS ID, 
     TO_DATE('11/01/2015') FRM_DT, 
     TO_DATE('12/31/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 3 AS ID, 
     TO_DATE('01/01/2015') FRM_DT, 
     TO_DATE('03/14/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 3 AS ID, 
     TO_DATE('03/15/2015') FRM_DT, 
     TO_DATE('11/30/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 3 AS ID, 
     TO_DATE('12/01/2015') FRM_DT, 
     TO_DATE('12/31/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 4 AS ID, 
     TO_DATE('02/01/2015') FRM_DT, 
     TO_DATE('05/30/2015') TO_DT 
    FROM DUAL 
    UNION 
    SELECT 4 AS ID, 
     TO_DATE('06/01/2015') FRM_DT, 
     TO_DATE('12/31/2015') TO_DT 
    FROM DUAL 
    )A 
)B 
GROUP BY B.ID, 
    B.COND; 

-----------------------------------OUTPUT------------------------------------------ 

ID FRM_DT   TO_DT 
4 02/01/2015 05/30/2015 
4 06/01/2015 12/31/2015 
1 01/01/2015 12/31/2015 
2 01/01/2015 06/30/2015 
2 11/01/2015 12/31/2015 
3 01/01/2015 12/31/2015 
-----------------------------------OUTPUT------------------------------------------