Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question.

Asked: August 03, 2018 - 5:18 am UTC

Last updated: August 06, 2018 - 10:33 am UTC

Version: 11g

Viewed 1000+ times

You Asked

Hi Team,
Could you please have a look ate below scenario and help me with building SQL please..

If there are intersecting date range, rows which has longer date interval should get returned.
i.e. One of the record for order 1 has date interval from 01/01/2017 to 01/12/2017,
however another order1 row exists wwith interval from 01/04/2017 to 01/12/2017 which falls under previous intrval i.e. 01/01/2017 to 01/12/2017.
in this case order1 with inerval 01/01/2017 to 01/12/2017 gets returned and should exclude interval 01/04/2017 to 01/12/2017.

NAME    EFF_FROM_DT EFF_TO_DT
------  ----------- ----------
order1 01/01/2017 01/12/2017
order1 01/01/2018 01/12/2018
order2 01/01/2017 01/12/2017
order2 01/01/2018 01/12/2018


-------------- Sample table and data ------------------
CREATE TABLE TAB1 
(NAME VARCHAR2(10 BYTE), 
EFF_FROM_DT DATE, 
EFF_TO_DT DATE
);

Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order1',to_date('01-JAN-17','DD-MON-RR'),to_date('01-DEC-17','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order1',to_date('01-APR-17','DD-MON-RR'),to_date('01-DEC-17','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order1',to_date('01-JAN-18','DD-MON-RR'),to_date('01-DEC-18','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order2',to_date('01-JAN-17','DD-MON-RR'),to_date('01-DEC-17','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order2',to_date('01-APR-17','DD-MON-RR'),to_date('01-DEC-17','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order2',to_date('01-JAN-18','DD-MON-RR'),to_date('01-DEC-18','DD-MON-RR'));
Insert into TAB1 (NAME,EFF_FROM_DT,EFF_TO_DT) values ('order2',to_date('01-AUG-18','DD-MON-RR'),to_date('01-DEC-18','DD-MON-RR'));

COMMIT;

and Chris said...

So you only want to show ranges where there is one row with a date falling in that range for the order?

If so, you can do something like:

- Generate all the possible dates between the start/end
- Join these to your table where the date falls between the start/end for each row
- Return those dates where there is only one date for that order
- Use the Tabibitosan method on the result of this to put these into groups
- Return the min & max date for each group

Which gives something like:

with min_max as (
  select min ( eff_from_dt ) first_start,
         max ( eff_to_dt ) last_end
  from   tab1
), all_dates as (
  select first_start + level - 1 dt 
  from   min_max
  connect by level <= ( last_end - first_start + 1 ) 
), exclusions as (
  select name, dt, count (*)
  from   tab1 t
  join   all_dates
  on     dt between eff_from_dt and eff_to_dt
  group  by name, dt
  having count (*) = 1
), grps as (
  select name, 
         dt,
         dt - row_number () over ( partition by name order by dt ) grp 
  from   exclusions
)
  select name, min ( dt ), max ( dt )
  from   grps
  group  by name, grp
  order  by name, grp;

NAME     MIN(DT)       MAX(DT)       
order1   01-JAN-2017   31-MAR-2017   
order1   01-JAN-2018   01-DEC-2018   
order2   01-JAN-2017   31-MAR-2017   
order2   01-JAN-2018   31-JUL-2018  


There are other ways you can solve this problem. There are 13 ways you can classify two date ranges in terms of how they overlap. How you classify which ranges overlap determines your solution.

Stew Ashton has written many blog posts on this topic. I recommend you read them:

https://stewashton.wordpress.com/2015/03/21/join-tables-on-date-ranges/
https://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/
https://stewashton.wordpress.com/tag/date-ranges-2/

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks and suggestion

Stew Ashton, August 04, 2018 - 11:24 am UTC

Thanks to Chris for the mention!

I read the question a bit differently from Chris. The OP seems to want to remove some rows and return the others as is. Using the terminology of Allen's Interval Algebra, a row should be removed if it "starts", "finishes", is "during" or is "equal" to another row.

For the first time, I am going to propose a solution using the MODEL clause with ITERATE:
select name, eff_from_dt, eff_to_dt
from (
  select * from tab1
  model
    partition by (name)
    dimension by (
      row_number()
        over(partition by name order by eff_from_dt, eff_to_dt desc)
      as rn
    )
    measures(eff_from_dt, eff_to_dt, 0 contained)
    rules iterate(1E9) until(contained[iteration_number+1] is null) (
      contained[rn > iteration_number+1] =
        case when eff_to_dt[cv()] <= eff_to_dt[iteration_number+1]
          then 1 else contained[cv()]
        end
    )
)
where contained = 0
order by 1,2;

NAME       EFF_FROM_D EFF_TO_DT 
---------- ---------- ----------
order1     2017-01-01 2017-12-01
order1     2018-01-01 2018-12-01
order2     2017-01-01 2017-12-01
order2     2018-01-01 2018-12-01

Best regards,
Stew
Chris Saxon
August 06, 2018 - 10:33 am UTC

I read the question a bit differently from Chris

Yeah, that's the problem with overlapping date queries. Without explicit and thorough test cases it's easy to write the wrong solution!

Which is why I pointed them to your blog to ensure they get it correct. ;)

Solution worked perfectly!!

A reader, August 04, 2018 - 1:35 pm UTC

Thanks Chris!
Shared blogs are also useful.

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.