Oracle - 将记录拆分为多个记录

时间:2017-03-15 23:45:20

标签: sql oracle plsql

我有一个每月计划的时间表。这张桌子也在那个月内有休息日。我需要一个结果集来告诉那个月的工作日和休息日。 例如

    CREATE TABLE SCHEDULE(sch_yyyymm varchar2(6), sch varchar2(20), sch_start_date date, sch_end_date date);

INSERT INTO SCHEDULE VALUES('201703','Working Days', to_date('03/01/2017','mm/dd/yyyy'), to_date('03/31/2017','mm/dd/yyyy'));

INSERT INTO SCHEDULE VALUES('201703','Off Day', to_date('03/05/2017','mm/dd/yyyy'), to_date('03/07/2017','mm/dd/yyyy'));

INSERT INTO SCHEDULE VALUES('201703','off Days', to_date('03/08/2017','mm/dd/yyyy'), to_date('03/10/2017','mm/dd/yyyy'));

INSERT INTO SCHEDULE VALUES('201703','off Days', to_date('03/15/2017','mm/dd/yyyy'), to_date('03/15/2017','mm/dd/yyyy'));

使用SQL或PL / SQL我需要将记录与工作日和关闭日分开。 从上面的记录我需要结果集:

201703  Working Days 03/01/2017 - 03/04/2017
201703  Off Days     03/05/2017 - 03/10/2017
201703  Working Days 03/11/2017 - 03/14/2017
201703  Off Days     03/15/2017 - 03/15/2017
201703  Working Days 03/16/2017 - 03/31/2017

感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

如果你有一个日期表,这会更容易。

您可以使用递归cte和join构建日期表。然后使用行号方法的差异将连续日期具有相同计划的行分类到一个组中,然后得到每个组的minmax,这将是给定sch的开始和结束日期。 我假设只有2个sch值Working DaysOff Day

with dates(dt) as (select date '2017-03-01' from dual
                   union all
                   select dt+1 from dates where dt < date '2017-03-31')
,groups as (select sch_yyyymm,dt,sch,
            row_number() over(partition by sch_yyyymm order by dt) 
            - row_number() over(partition by sch_yyyymm,sch order by dt) as grp       
            from (select s.sch_yyyymm,d.dt,
                  /*This condition is to avoid a given date with 2 sch values, as 03-01-2017 - 03-31-2017 are working days 
                    on one row and there is an Off Day status for some of these days. 
                    In such cases Off Day would be picked up as sch*/
                  case when count(*) over(partition by d.dt) > 1 then min(s.sch) over(partition by d.dt) else s.sch end as sch
                  from dates d
                  join schedule s on d.dt >= s.sch_start_date and d.dt <= s.sch_end_date 
                 ) t
            )    
select sch_yyyymm,sch,min(dt) as start_date,max(dt) as end_date
from groups 
group by sch_yyyymm,sch,grp 

我无法在Oracle中运行递归cte。这是一个使用SQL Server的演示。

Sample Demo in SQL Server

答案 1 :(得分:0)

编辑:我有一点想法,这种方法适用于上面的插入记录 - 但是,它错过了没有连续“休息日”期间的记录。我需要更多的思考,然后进行一些更改

我使用leadlag函数以及自我加入进行了测试。

结果是你自己加入“Off Days”到现有的桌子上以找到重叠。然后计算每条记录两侧的开始/结束日期。然后通过一些逻辑让我们确定将哪个日期用作最终的开始/结束日期。

SQL小提琴here - 我使用Postgres,因为Oracle函数无效但它应该转换为ok。

select  sch,
        /* Work out which date to use as this record's Start date */
        case when prev_end_date is null then sch_start_date
             else off_end_date + 1
        end as final_start_date,
        /* Work out which date to use as this record's end date */
        case when next_start_date is null then sch_end_date
             when next_start_date is not null and prev_end_date is not null then next_start_date - 1
             else off_start_date - 1
        end as final_end_date
from (
select  a.*,
        b.*,
        /* Get the start/end dates for the records on either side of each working day record */
        lead( b.off_start_date ) over( partition by a.sch_start_date order by b.off_start_date ) as next_start_date,
        lag( b.off_end_date ) over( partition by a.sch_start_date order by b.off_start_date ) as prev_end_date
from    (
          /* Get all schedule records */
          select sch,
                 sch_start_date,
                 sch_end_date
          from   schedule
        ) as a
        left join
        (
          /* Get all non-working day schedule records */
          select sch as off_sch,
                 sch_start_date as off_start_date,
                 sch_end_date as off_end_date
          from   schedule
          where  sch <> 'Working Days'
        ) as b
        /* Join on "Off Days" that overlap "Working Days" */
        on a.sch_start_date <= b.off_end_date
        and a.sch_end_date >= b.off_start_date
        and a.sch <> b.off_sch
 ) as c
 order by final_start_date