根据时间将行拆分为多行

Split row into multiple rows based on time

假设在 Oracle 10g 中,我有这样一个 table:

id    | start_date | end_date   | value   | parent_id
----------------------------------------------------
1     | 01-01-2001 | 01-01-2002 | aaaaaa  | 3
2     | 01-01-2003 | 01-01-2004 | bbbbbb  | 3
3     | 01-01-2000 | 01-01-2005 | cccccc  | 4
4     | 01-01-1999 | 01-01-2006 | dddddd  |null

我希望看到任何空白都由父级填补而不重叠。更具体地说,结果应该是这样的:

start_date | end_date   | value  | depth 
-----------------------------------------
01-01-1999 | 01-01-2000 | dddddd | 2
01-01-2000 | 01-01-2001 | cccccc | 1
01-01-2001 | 01-01-2002 | aaaaaa | 0
01-01-2002 | 01-01-2003 | cccccc | 1
01-01-2003 | 01-01-2004 | bbbbbb | 0
01-01-2004 | 01-01-2005 | cccccc | 1
01-01-2005 | 01-01-2006 | dddddd | 2

深度是得到值的父级的个数,可以超过2个,所以最好用递归。

您可以假设具有相同父项的期间没有重叠。所有这些都没有使用存储过程,但可以随意使用 CTE、window 函数等。

是的,这是可能的。虽然不知道它的表现如何。

查询分为 3 个主要 CTE:

  1. 为输出目的生成所需日期范围的 CTE
  2. 计算树中最大深度的 CTE
  3. 一个 CTE,用于将所有数据连接在一起并使用 row_number() window 函数按深度标记更多 "desirable" 行。

查询:

with date_ranges_cte as (
  select add_months(date '1999-01-01', (rownum-1) * 12) as start_date,
         add_months(date '1999-01-01', rownum * 12) as end_date
    from dual
  connect by rownum <= 7
), max_level_cte as (
  select max(level) as max_level
    from tbl
   start with parent_id is null
 connect by prior id = parent_id
), main_cte as (
  select d.start_date, 
         d.end_date,
         t.value,
         t.lvl,
         row_number() over (partition by d.start_date, d.end_date order by t.lvl desc, t.id) as rn
    from date_ranges_cte d
    join (select t.*, level as lvl
            from tbl t
           start with parent_id is null
         connect by prior id = parent_id) t
      on ((d.start_date >= t.start_date and d.start_date < t.end_date)
          or (d.end_date > t.start_date and d.end_date <= t.end_date))
)
select m.start_date,
       m.end_date,
       m.value,
       l.max_level - m.lvl as depth
  from main_cte m
  cross join max_level_cte l
 where m.rn = 1
 order by m.start_date

如果你可以颠倒 depth 的定义,那么我们可以去掉其中一个 CTE:

with date_ranges_cte as (
  select add_months(date '1999-01-01', (rownum-1) * 12) as start_date,
         add_months(date '1999-01-01', rownum * 12) as end_date
    from dual
  connect by rownum <= 7
), main_cte as (
  select d.start_date, 
         d.end_date,
         t.value,
         t.lvl,
         row_number() over (partition by d.start_date, d.end_date order by t.lvl desc, t.id) as rn
    from date_ranges_cte d
    join (select t.*, level as lvl
            from tbl t
           start with parent_id is null
         connect by prior id = parent_id) t
      on ((d.start_date >= t.start_date and d.start_date < t.end_date)
          or (d.end_date > t.start_date and d.end_date <= t.end_date))
)
select start_date,
       end_date,
       value,
       lvl - 1 as depth
  from main_cte
 where rn = 1
 order by start_date