PL/SQL: 如何将具有连续时间范围的多行转换为覆盖整个时间范围的一行?
PL/SQL: How to convert multiple rows with continuous time frames into one single row convering the whole time frame?
假设我有以下数据库 table:
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的记录汇总成一行覆盖整个时间范围,如下:
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 的 3 行可以转换为具有最小起始日期和最大截止日期的 1 行。 id 2 的 2 行将保持不变,因为时间范围不连续。
我正在考虑通过游标使用循环来执行此操作,但我可能会使事情复杂化。
有更好的主意吗?也许只有 SQL 个查询?
您可以在具有一些分析和聚合函数的阶段执行此操作:
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 中,我从 from_dt 中减去 to_dt 的滞后以找到连续记录之间的距离 (dst) 并为每个记录 (rn) 生成 row_number。在 T3 中,我从 dst 的 运行 总和中减去 rn 以生成组 ID (grp)。最后在输出阶段,我将 from_dt 和 to_dt 的最小值和最大值分别按 ID 和 grp 列分组。
您可以使用分层查询来实现,如下所示:
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
You can try here some analytical functions which can really simplify
the scenario. Hope this below snippet helps. Let me know for any
issues.
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------------------------------------------
假设我有以下数据库 table:
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的记录汇总成一行覆盖整个时间范围,如下:
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 的 3 行可以转换为具有最小起始日期和最大截止日期的 1 行。 id 2 的 2 行将保持不变,因为时间范围不连续。
我正在考虑通过游标使用循环来执行此操作,但我可能会使事情复杂化。 有更好的主意吗?也许只有 SQL 个查询?
您可以在具有一些分析和聚合函数的阶段执行此操作:
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 中,我从 from_dt 中减去 to_dt 的滞后以找到连续记录之间的距离 (dst) 并为每个记录 (rn) 生成 row_number。在 T3 中,我从 dst 的 运行 总和中减去 rn 以生成组 ID (grp)。最后在输出阶段,我将 from_dt 和 to_dt 的最小值和最大值分别按 ID 和 grp 列分组。
您可以使用分层查询来实现,如下所示:
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
You can try here some analytical functions which can really simplify the scenario. Hope this below snippet helps. Let me know for any issues.
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------------------------------------------