选择不相交的时段的最小/最大日期

时间:2017-11-10 10:42:31

标签: sql oracle

实施例!我有一个有4列的表。日期格式dd.MM.yy

id   ban   start     end
1    1     01.01.15  31.12.18
1    1     02.02.15  31.12.18
1    1     05.04.15  31.12.17

在这种情况下,第2行和第3行的日期包含在第1行的日期

1    1     02.04.19  31.12.20
1    1     05.05.19  31.12.20

在这种情况下,第5行的日期包含在第4行的日期中。基本上我们有2个不相交的句号。

01.01.15  31.12.18

02.04.19  31.12.20

日期从一个时期开始到另一个时期结束的情况是不可能的。最终结果应如下所示

1    1     01.01.15  31.12.18
1    1     02.04.19  31.12.20

我尝试使用分析函数(LAG)

select id
  , ban
  , case 
    when start >= nvl(lag(start) over (partition by id, ban order by start, end asc), start) 
    and end <= nvl(lag(end) over (partition by id, ban order by start, end asc), end)
    then nvl(lag(start) over (partition by id, ban order by start, end asc), start) 
    else start
    end as start
  , case 
    when start >= nvl(lag(start) over (partition by id, ban order by start, end asc), start) 
    and end <= nvl(lag(end) over (partition by id, ban order by start, end asc), end)
    then nvl(lag(end) over (partition by id, ban order by start, end asc), end)
    else end
    end as end
from table

在我订购行的情况下,如果当前日期包含在之前,我会替换它们。如果我只有2行,它就可以工作。例如这个

1   1    08.09.15   31.12.99
1   1    31.12.15   31.12.99

变成这个

1   1    08.09.15   31.12.99
1   1    08.09.15   31.12.99

然后我可以按所有字段分组并获得我想要的内容,但是如果有更多

1   2        13.11.15   31.12.99
1   2        31.12.15   31.12.99
1   2        16.06.15   31.12.99

我得到了

1   2        16.06.15   31.12.99
1   2        16.06.15   31.12.99
1   2        13.11.15   31.12.99

我理解为什么会这样,但我该如何解决呢?多次运行查询不是一种选择。

2 个答案:

答案 0 :(得分:1)

此查询看起来很有希望:

-- test data
with t(id, ban, dtstart, dtend) as (
    select 1, 1, date '2015-01-01', date '2015-03-31' from dual union all
    select 1, 1, date '2015-02-02', date '2015-03-31' from dual union all
    select 1, 1, date '2015-03-15', date '2015-03-31' from dual union all
    select 1, 1, date '2015-08-05', date '2015-12-31' from dual union all
    select 1, 2, date '2015-01-01', date '2016-12-31' from dual union all
    select 2, 1, date '2016-01-01', date '2017-12-31' from dual),
-- end of test data
step1 as (select id, ban, dt, to_number(inout) direction 
            from t unpivot (dt for inout in (dtstart as '1', dtend as '-1'))),
step2 as (select distinct id, ban, dt, direction, 
                 sum(direction) over (partition by id, ban order by dt) sm 
            from step1),
step3 as (select id, ban, direction, dt dt1, 
                 lead(dt) over (partition by id, ban order by dt) dt2 
            from step2      
            where (direction = 1 and sm = 1) or (direction = -1 and sm = 0) )
select id, ban, dt1, dt2 
  from step3 where direction = 1 order by id, ban, dt1
  • 第1步 - 取消日期并指定1作为开始日期,-1表示结束日期 日期(列direction
  • step2 - 为direction
  • 添加累计金额
  • step3 - 仅过滤有趣的日期,使用lead()
  • 转动第二个日期

您可以缩短此语法,我将其划分为步骤以显示正在进行的操作。

结果:

    ID        BAN DT1         DT2
------ ---------- ----------- -----------
     1          1 2015-01-01  2015-03-31
     1          1 2015-08-05  2015-12-31
     1          2 2015-01-01  2016-12-31
     2          1 2016-01-01  2017-12-31

我认为对于不同的(ID,BAN),我们必须单独进行计算。如果不是 - 请更改sum()lead()中的分区和排序。

Pivotunpivot适用于Oracle 11及更高版本,适用于您需要的早期版本case when

BTW - START是Oracle中的保留字,因此在我的示例中,我稍微更改了列名称。

答案 1 :(得分:0)

我喜欢通过识别期间开始,然后执行累积总和来定义组,以及最终聚合来做到这一点:

select id, ban, min(start), max(end)
from (select t.*, sum(start_flag) over (partition by id, bin order by start) as grp
      from (select t.*,
                   (case when exists (select 1
                                      from t t2
                                      where t2.id = t.id and t2.ban = t.ban and
                                            t.start <= t2.end and t.end >= t2.start and
                                            t.start <> t2.start and t.end <> t2.end
                                     )
                         then 0 else 1
                    end) as start_flag
            from t
           ) t
    ) t
group by id, ban, grp;