根据时间将行拆分为多行

时间:2016-07-27 12:38:10

标签: sql oracle oracle10g common-table-expression window-functions

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

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,窗口函数等。

1 个答案:

答案 0 :(得分:1)

是的,这是可能的。不知道它会有多好。

查询分为3个主要CTE:

  1. 用于生成输出目的所需日期范围的CTE
  2. 用于计算树中最大深度的CTE
  3. 一个CTE,用于将所有数据连接在一起,并使用row_number()窗口函数按深度标记更“理想”的行。
  4. 查询:

    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