Skip to Main Content
  • Questions
  • Complicated query to analyse utilization period

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Samer.

Asked: November 08, 2016 - 10:12 am UTC

Last updated: November 15, 2016 - 1:13 am UTC

Version: 11.2.0.1.0

Viewed 1000+ times

You Asked

Dear Gents

I have a query which is giving the available quantity in each location on specific dates:

Location Tdate RunningQty
CA02D003A 31-Jul-16 1152
CA02D003A 30-Sep-16 1092
CA02D003A 11-Oct-16 500
CA02D003A 13-Oct-16 0
CA02D003A 20-Oct-16 200
CA02D003A 24-Oct-16 0
CA02D003A 26-Oct-16 1752
CA02D004A 31-Aug-16 0
CA02D004A 30-Sep-16 96
CA02D004A 12-Oct-16 40
CA02D004A 18-Oct-16 70
CA02D004A 22-Oct-16 0
CA02D004A 26-Oct-16 20
CA02D004A 30-Oct-16 0

Based on this, I want to know for each Location how long it has been used in October.
So for the first location, it was used from 01-Oct until 13-Oct then from 20-Oct until 24-Oct then from 26-Oct until 31-Oct.

Appreciate your support.

and Chris said...

Here's one way to do it. To set it up, first you want to:

- Generate a list of dates in October:

select date'2016-10-01'+rownum-1 dt from dual connect by level <= 31


- Convert the list of transactions into start/end date periods. You can do this using lead:

select loc, dt, lead(dt) over (partition by loc order by dt) nd, qty
from   t


Then join the October dates that fall in the calculated periods. Exclude those where the quantity is zero:

with oct_dates as (
  select date'2016-10-01'+rownum-1 dt from dual connect by level <= 31
), ranges as (
  select loc, dt, lead(dt) over (partition by loc order by dt) nd, qty
  from   t
)
  select loc, 
         d.dt 
  from   ranges r
  join   oct_dates d
  on     d.dt >= r.dt 
  and    d.dt < nvl(r.nd, date'2016-11-01')
  where  qty > 0;

LOC        DT         
CA02D003A  01-Oct-16  
CA02D004A  01-Oct-16  
CA02D003A  02-Oct-16  
CA02D004A  02-Oct-16  
CA02D003A  03-Oct-16  
CA02D004A  03-Oct-16 
...


This gives you the dates each location was in use. If you just want the number of days, group by loc and count:

create table t (
  loc varchar2(20),
  dt  date,
  qty int
);
alter session set nls_date_format = 'dd-Mon-yy';
insert into t values ('CA02D003A', '31-Jul-16', 1152);
insert into t values ('CA02D003A', '30-Sep-16', 1092);
insert into t values ('CA02D003A', '11-Oct-16', 500);
insert into t values ('CA02D003A', '13-Oct-16', 0);
insert into t values ('CA02D003A', '20-Oct-16', 200);
insert into t values ('CA02D003A', '24-Oct-16', 0);
insert into t values ('CA02D003A', '26-Oct-16', 1752);
insert into t values ('CA02D004A', '31-Aug-16', 0);
insert into t values ('CA02D004A', '30-Sep-16', 96);
insert into t values ('CA02D004A', '12-Oct-16', 40);
insert into t values ('CA02D004A', '18-Oct-16', 70);
insert into t values ('CA02D004A', '22-Oct-16', 0);
insert into t values ('CA02D004A', '26-Oct-16', 20);
insert into t values ('CA02D004A', '30-Oct-16', 0);

with oct_dates as (
  select date'2016-10-01'+rownum-1 dt from dual connect by level <= 31
), ranges as (
  select loc, dt, lead(dt) over (partition by loc order by dt) nd, qty
  from   t
)
  select loc, 
         count(d.dt)
  from   ranges r
  join   oct_dates d
  on     d.dt >= r.dt 
  and    d.dt < nvl(r.nd, date'2016-11-01')
  where  qty > 0
  group  by loc;

LOC        COUNT(D.DT)  
CA02D003A  22           
CA02D004A  25


If you want to know which periods it was in use, you can use the Tabibitosan method to group consecutive dates together and get the min start and max end date for each:

with oct_dates as (
  select date'2016-10-01'+rownum-1 dt from dual connect by level <= 31
), ranges as (
  select loc, dt, lead(dt) over (partition by loc order by dt) nd, qty
  from   t
), grps as (
  select loc, 
         d.dt - row_number() over (partition by loc order by d.dt) grp,
         d.dt 
  from   ranges r
  join   oct_dates d
  on     d.dt >= r.dt 
  and    d.dt < nvl(r.nd, date'2016-11-01')
  where  qty > 0
)
  select loc, min(dt), max(dt) from grps
  group  by loc, grp
  order  by 1, 2;

LOC        MIN(DT)    MAX(DT)    
CA02D003A  01-Oct-16  12-Oct-16  
CA02D003A  20-Oct-16  23-Oct-16  
CA02D003A  26-Oct-16  31-Oct-16  
CA02D004A  01-Oct-16  21-Oct-16  
CA02D004A  26-Oct-16  29-Oct-16


You can find out more about the Tabibitosan method by watching this:

https://www.youtube.com/watch?v=yvimYixXo2Q

Or reading this:

http://rwijk.blogspot.co.uk/2014/01/tabibitosan.html

Rating

  (6 ratings)

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

Comments

Amazing solution

Samer, November 09, 2016 - 11:35 am UTC

Thank you again Chris.
I need a few days to figure out how this works, I feel rusty.

But I have a small issue it is counting the days as difference so between 1-Oct and 10-Oct it is counting as 9 days instead of 10.
How can I fix this?
Chris Saxon
November 09, 2016 - 3:00 pm UTC

Did the quantity become zero on the 10th Oct? If so, does this mean the location was used up to and INCLUDING 10th, or up to EXCLUDING the 10th?

If it's including and quantity goes to 100 on 11th, does that mean it was used continuously? Or was there a day it's not used?

You need to adjust the inequalities (<, <=, etc.) in the join between ranges and dates based on your answers to these questions.

More details

Samer, November 09, 2016 - 12:39 pm UTC

Running:
select location, TDate FromDate, lead(TDate) over (partition by location order by TDate) TODate, RunningQty from billingDB where location in('202BE007B', '202BE009E');
is resulting in:
Location FromDate ToDate Qty
202BE007B 30-SEP-16 13-OCT-16 0
202BE007B 13-OCT-16 1740
202BE009E 30-SEP-16 13-OCT-16 0
202BE009E 13-OCT-16 1350

So both locations were being used the whole October month.

Using the last query I am getting:
Location FromDate ToDate
202BE007B 13-OCT-16 31-OCT-16
202BE009E 13-OCT-16 31-OCT-16
Chris Saxon
November 09, 2016 - 3:02 pm UTC

What's the raw data here? Your original table doesn't have from/to dates!

Explanation

Samer, November 09, 2016 - 8:19 pm UTC

Dear Chris

Please ignore my last data statement since it had a blunder.
To answer your questions the day where the quantity is zero should be counted. Moreover if the next day the location is replenished then it is a continuation.
Chris Saxon
November 10, 2016 - 11:33 am UTC

Could you give sample data (in the form of inserts) and the output you expect?

More details

Samer, November 10, 2016 - 12:32 pm UTC

Dear Chris

Here are all the scenarios:

CA02D003A 30-Sep-16 1092
CA02D003A 11-Oct-16 500
CA02D003A 13-Oct-16 0
CA02D003A 20-Oct-16 200
CA02D003A 24-Oct-16 0
CA02D003A 26-Oct-16 1500
In this case the intervals are:
1-Oct-16 until 13-Oct-16 = 13 days
20-Oct-16 until 24-Oct-16 = 5 days
26-Oct-16 until 31-Oct-16 = 6 days
Total: 24 days

DI13F001B 30-Sep-16 500
DI13F001B 26-Oct-16 400
In this case the intervals are:
1-Oct-16 until 31-Oct-16 = 31 days

DI13F001A 30-Sep-16 450
DI13F001A 14-Oct-16 250
DI13F001A 24-Oct-16 0
DI13F001A 25-Oct-16 50
DI13F001A 26-Oct-16 20
DI13F001A 27-Oct-16 0
In this case the intervals are:
1-Oct-16 until 24-Oct-16 = 24 days
25-Oct-16 until 27-Oct-16 = 3 days
Total: 27 days

DI13F001C 15-Sep-16 0
DI13F001C 18-Oct-16 200
DI13F001C 24-Oct-16 0
DI13F001C 25-Oct-16 100
In this case the intervals are:
18-Oct-16 until 24-Oct-16 = 7 days
25-Oct-16 until 31-Oct-16 = 7 days
Total: 14 days
Chris Saxon
November 10, 2016 - 5:27 pm UTC

Change the strictly less than to less than or equal to in the join between ranges and oct_dates:

d.dt < nvl(r.nd, date'2016-11-01')

becomes:

d.dt <= nvl(r.nd, date'2016-11-01')

And ensure you count distinct dates - count(distinct d.dt)

amazing

Anubha, November 10, 2016 - 3:23 pm UTC

really very easy n technically feasible solution Chris

It worked

Samer Kharsa, November 14, 2016 - 4:39 pm UTC

Thank you Chris it worked.

I used the following:

with
bill_month as (select date'2016-10-01'+rownum-1 dt from dual connect by level <= 31),
ranges as (select location loc, TDate dt, lead(TDate) over (partition by location order by TDate) nd, RunningQty qty from BillingDB),
grps as (select loc, d.dt - row_number() over (partition by loc order by d.dt) grp, d.dt from ranges r join bill_month d
on d.dt >= r.dt and d.dt <= nvl(r.nd, date'2016-11-01') where qty > 0),
fin as (select loc, min(dt) FDate, max(dt) TDate, (max(dt) - min(dt)) as ndays from grps group by loc, grp order by 1, 2),
fil as (select loc, count(distinct d.dt) Days from ranges r join bill_month d on d.dt >= r.dt and d.dt <= nvl(r.nd, date'2016-11-01') where qty >0 group by loc)

select loc, Days from fil order by loc;
Connor McDonald
November 15, 2016 - 1:13 am UTC

glad we could help

More to Explore

Analytics

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