在oracle中打印非重叠日期块

时间:2015-07-03 05:22:00

标签: oracle plsql nested-loops

我有下表:

ID START_DATE    END_DATE           
 1 01.06.2015  20.06.2015 
 2 05.06.2015  25.06.2015 
 3 03.06.2015  10.06.2015 
 4 07.06.2015  23.06.2015 
 5 21.06.2015  30.06.2015 
 6 02.06.2015  10.06.2015 
 7 05.06.2015  15.06.2015 
 8 05.06.2015  08.06.2015 
 9 16.06.2015  20.06.2015 

表格中有重叠的时间表。例如,03/06/2015-10/06/2015位于1/06/2015-20/06/2015之间。事实上,非重叠时间轴是1/06/201505/06/201521/06/2015。我必须检索这些值。我知道我必须使用嵌套循环来比较特定日期和每隔一个日期。我所做的是:

DECLARE
 min_sd DATE;
 max_ed DATE;
 sd DATE;
 ed DATE;
 i INT:=1;
 j INT:=1;

PROCEDURE date_block
IS

BEGIN

  WHILE i<=9 loop
  SELECT start_date,end_date INTO min_sd,max_ed FROM sd_ed WHERE id=i;

    WHILE j<=9 loop
     SELECT start_date,end_date INTO sd,ed FROM sd_ed WHERE id=j;

      IF min_sd<=sd AND max_ed>=ed THEN
        j:=j+1;

      ELSE
        Dbms_Output.put_line(sd||' - '||ed);
        j:=j+1;

       END IF;
      i:=i+1;

    END LOOP;

  END loop;
END;

BEGIN
  date_block();
END;

我得到的输出是:

05-JUN-15 - 25-JUN-15
07-JUN-15 - 23-JUN-15
21-JUN-15 - 30-JUN-15

我认为这些日期没有与表格中的日期进行比较。任何人都可以提供帮助吗?我正在使用oracle。

仅使用SQL,

SELECT a.*
FROM   (SELECT *
        FROM  sd_ed
        ORDER BY id) a
WHERE   NOT EXISTS (SELECT 1
               FROM   sd_ed_test b
               WHERE  b.start_date <= a.start_date
               AND    b.end_date   >= a.end_date
               AND    b.id         <  a.id
               );

2 个答案:

答案 0 :(得分:1)

这是一个纯SQL解决方案:

SQL Fiddle

Oracle 11g R2架构设置

create table sd_ed as with sd_ed(ID, START_DATE, END_DATE) as (
select 1, to_date('01.06.2015','dd.mm.yyyy'), to_date('20.06.2015','dd.mm.yyyy') from dual union all
select 2, to_date('05.06.2015','dd.mm.yyyy'), to_date('25.06.2015','dd.mm.yyyy') from dual union all
select 3, to_date('03.06.2015','dd.mm.yyyy'), to_date('10.06.2015','dd.mm.yyyy') from dual union all
select 4, to_date('07.06.2015','dd.mm.yyyy'), to_date('23.06.2015','dd.mm.yyyy') from dual union all
select 5, to_date('21.06.2015','dd.mm.yyyy'), to_date('30.06.2015','dd.mm.yyyy') from dual union all
select 6, to_date('02.06.2015','dd.mm.yyyy'), to_date('10.06.2015','dd.mm.yyyy') from dual union all
select 7, to_date('05.06.2015','dd.mm.yyyy'), to_date('15.06.2015','dd.mm.yyyy') from dual union all
select 8, to_date('05.06.2015','dd.mm.yyyy'), to_date('08.06.2015','dd.mm.yyyy') from dual union all
select 9, to_date('16.06.2015','dd.mm.yyyy'), to_date('20.06.2015','dd.mm.yyyy') from dual
) select * from sd_ed;

查询1

with super as(
  -- Select the super ranges.  All other ranges are
  -- completely contained in at least one super range
  select id, start_date, end_date
    from sd_ed a
   where not exists (select 1
                       from sd_ed b
                      where a.id <> b.id
                        and b.start_date <= a.start_date
                        and a.end_date <= b.end_date)
), hier(id, start_date, end_date) as (
-- Select all record with a start date not between
-- any other records start and end dates
select id, start_date, end_date from super
 where not exists(select 1 from super d1
                   where d1.id <> super.id
                     and super.start_date between d1.start_date and d1.end_date
                     and super.start_date <> d1.start_date)
-- Recursively select records that overlap the current range
-- but with end dates after the end date of the current range
union all
select sd_ed.id
     , prev.start_date
     , greatest(sd_ed.end_date, prev.end_date)
  from hier prev
  join sd_ed
    on sd_ed.id <> prev.id
   and sd_ed.start_date <= prev.end_date
   and prev.end_date < sd_ed.end_Date
)
-- Get the max end_date for each start date.
-- Start Dates are already minimum for any range.
select start_date, max(end_Date) end_date
  from hier
 group by start_date

<强> Results

|             START_DATE |               END_DATE |
|------------------------|------------------------|
| June, 01 2015 00:00:00 | June, 30 2015 00:00:00 |

对于此数据集,确实不需要识别超级范围,因为分层查询可以很好地处理它,但对于较大的数据集,这种初始修剪将减少分层查询需要完成的工作量。

答案 1 :(得分:0)

您尚未清楚地解释如何识别不重叠的日期范围。这是一个解决方案,列出每个范围之间的所有日期,然后丢弃所有重复的日期(只留下那些只出现在单个范围内的日子),然后将它们分组为连续几天的数据块。

SQL Fiddle

Oracle 11g R2架构设置

create table sd_ed (ID, START_DATE, END_DATE) as
select 1, to_date('01.06.2015','dd.mm.yyyy'), to_date('20.06.2015','dd.mm.yyyy') from dual union all
select 2, to_date('05.06.2015','dd.mm.yyyy'), to_date('25.06.2015','dd.mm.yyyy') from dual union all
select 3, to_date('03.06.2015','dd.mm.yyyy'), to_date('10.06.2015','dd.mm.yyyy') from dual union all
select 4, to_date('07.06.2015','dd.mm.yyyy'), to_date('23.06.2015','dd.mm.yyyy') from dual union all
select 5, to_date('21.06.2015','dd.mm.yyyy'), to_date('30.06.2015','dd.mm.yyyy') from dual union all
select 6, to_date('02.06.2015','dd.mm.yyyy'), to_date('10.06.2015','dd.mm.yyyy') from dual union all
select 7, to_date('05.06.2015','dd.mm.yyyy'), to_date('15.06.2015','dd.mm.yyyy') from dual union all
select 8, to_date('05.06.2015','dd.mm.yyyy'), to_date('08.06.2015','dd.mm.yyyy') from dual union all
select 9, to_date('16.06.2015','dd.mm.yyyy'), to_date('20.06.2015','dd.mm.yyyy') from dual;

查询1

WITH days AS (
  SELECT COLUMN_VALUE AS Day
  FROM   sd_ed s,
         TABLE(
           CAST(
             MULTISET(
               SELECT s.START_DATE + LEVEL - 1
               FROM   DUAL
               CONNECT BY s.START_DATE + LEVEL - 1 <= s.END_DATE
             )
             AS SYS.ODCIDATELIST
           )
         ) d
  GROUP BY COLUMN_VALUE
  HAVING COUNT(1) = 1
),
Group_Changes AS (
  SELECT Day,
         CASE WHEN LAG( Day ) OVER ( ORDER BY Day ) + INTERVAL '1' DAY = Day THEN 0 ELSE 1 END AS Change_Group
  FROM   Days
),
Groups AS (
  SELECT Day,
         SUM( Change_Group ) OVER ( ORDER BY Day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS group_id
  FROM   Group_Changes
)
SELECT MIN( Day ) AS start_date,
       MAX( Day ) AS end_date
FROM   Groups
GROUP BY Group_Id
ORDER BY 1,2

<强> Results

|             START_DATE |               END_DATE |
|------------------------|------------------------|
| June, 01 2015 00:00:00 | June, 01 2015 00:00:00 |
| June, 26 2015 00:00:00 | June, 30 2015 00:00:00 |

但是,如果您只需要表格中的日期范围列表,那么您可以这样做(与上面没有HAVING COUNT(1) = 1行的情况相同):

查询2

WITH days AS (
  SELECT COLUMN_VALUE AS Day
  FROM   sd_ed s,
         TABLE(
           CAST(
             MULTISET(
               SELECT s.START_DATE + LEVEL - 1
               FROM   DUAL
               CONNECT BY s.START_DATE + LEVEL - 1 <= s.END_DATE
             )
             AS SYS.ODCIDATELIST
           )
         ) d
  GROUP BY COLUMN_VALUE
),
Group_Changes AS (
  SELECT Day,
         CASE WHEN LAG( Day ) OVER ( ORDER BY Day ) + INTERVAL '1' DAY = Day THEN 0 ELSE 1 END AS Change_Group
  FROM   Days
),
Groups AS (
  SELECT Day,
         SUM( Change_Group ) OVER ( ORDER BY Day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS group_id
  FROM   Group_Changes
)
SELECT MIN( Day ) AS start_date,
       MAX( Day ) AS end_date
FROM   Groups
GROUP BY Group_Id
ORDER BY 1,2

<强> Results

|             START_DATE |               END_DATE |
|------------------------|------------------------|
| June, 01 2015 00:00:00 | June, 30 2015 00:00:00 |