Reputation: 540
Let's suppose I have the following table. How can I can separately calculate the delay for each month.
Req start_dt end_dt
1 1/2/2017 3/5/2017
2 5/2/2017 7/6/2017
I want the result to be like the following table.
Req start_dt end_dt delay MM
1 1/2/2017 3/5/2017 30 1
1 1/2/2017 3/5/2017 28 2
1 1/2/2017 3/5/2017 5 3
2 5/2/2017 7/6/2017 30 5
2 5/2/2017 7/6/2017 30 6
2 5/2/2017 7/6/2017 6 7
Upvotes: 3
Views: 95
Reputation: 191275
You need to split your date ranges into multiple months, with the full range for intermediate months and partial ranges for the start and end months. One way to do that is with recursive subquery factoring
with rcte (req, start_dt, end_dt, period_start_dt, period_end_dt) as (
select req, start_dt, end_dt, start_dt,
case when trunc(end_dt, 'MM') = trunc(start_dt, 'MM') then end_dt
else last_day(start_dt) end
from your_table
union all
select req, start_dt, end_dt, add_months(trunc(period_start_dt, 'MM'), 1),
case when trunc(end_dt, 'MM') = add_months(trunc(period_start_dt, 'MM'), 1) then end_dt
else last_day(add_months(trunc(period_start_dt, 'MM'), 1) ) end
from rcte
where trunc(end_dt, 'MM') > trunc(period_start_dt, 'MM')
)
select req, start_dt, end_dt, period_start_dt, period_end_dt,
period_end_dt - period_start_dt + 1 as delay,
extract(month from period_start_dt) as mm
from rcte
order by req, period_start_dt, period_end_dt;
REQ START_DT END_DT PERIOD_STA PERIOD_END DELAY MM
---------- ---------- ---------- ---------- ---------- ---------- ----------
1 2017-01-02 2017-03-05 2017-01-02 2017-01-31 30 1
1 2017-01-02 2017-03-05 2017-02-01 2017-02-28 28 2
1 2017-01-02 2017-03-05 2017-03-01 2017-03-05 5 3
2 2017-05-02 2017-07-06 2017-05-02 2017-05-31 30 5
2 2017-05-02 2017-07-06 2017-06-01 2017-06-30 30 6
2 2017-05-02 2017-07-06 2017-07-01 2017-07-06 6 7
The recursive CTE has an anchor member which gets the initial data from your table, and calculates the start and end of the first period. The period start is the original range start date; the period end is either the range end date (if it's in the same month), or the end of that month.
The recursive member then uses the values from the anchor and generates a new period, which will start at the beginning of the following month, and again end either at the original range end date or the end of that month.
Once you have those period start/end dates it's simple to calculate the difference using normal date subtraction. (I've left the period start/end dates in the output to try and make it a bit clearer; just remove from the final select list if you don't want them.)
Slightly simpler calculation for the period start/end dates:
with rcte (req, start_dt, end_dt, period_start_dt, period_end_dt) as (
select req, start_dt, end_dt, start_dt, least(end_dt, last_day(start_dt))
from your_table
union all
select req, start_dt, end_dt, add_months(trunc(period_start_dt, 'MM'), 1),
least(end_dt, last_day(add_months(trunc(period_start_dt, 'MM'), 1)))
from rcte
where trunc(end_dt, 'MM') > trunc(period_start_dt, 'MM')
)
select req, start_dt, end_dt,
period_end_dt - period_start_dt + 1 as delay,
extract(month from period_start_dt) as mm
from rcte
order by req, period_start_dt, period_end_dt;
REQ START_DT END_DT DELAY MM
---------- ---------- ---------- ---------- ----------
1 2017-01-02 2017-03-05 30 1
1 2017-01-02 2017-03-05 28 2
1 2017-01-02 2017-03-05 5 3
2 2017-05-02 2017-07-06 30 5
2 2017-05-02 2017-07-06 30 6
2 2017-05-02 2017-07-06 6 7
Upvotes: 2
Reputation: 167972
You can do it with correlated hierarchical queries:
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Req, start_dt, end_dt ) AS
SELECT 1, DATE '2017-01-02', DATE '2017-03-05' FROM DUAL UNION ALL
SELECT 2, DATE '2017-05-02', DATE '2017-07-06' FROM DUAL;
Query 1:
SELECT t.*,
LEAST( LAST_DAY( d.COLUMN_VALUE ), t.end_dt )
- GREATEST( d.COLUMN_VALUE, t.start_dt ) + 1 AS delay,
EXTRACT( MONTH FROM d.COLUMN_VALUE ) AS MM
FROM table_name t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT ADD_MONTHS( TRUNC( t.start_dt, 'MM' ), LEVEL - 1 )
FROM DUAL
CONNECT BY LEVEL <= MONTHS_BETWEEN( t.end_dt, TRUNC( t.start_dt, 'MM' ) ) + 1
) AS SYS.ODCIDATELIST
)
) d
| REQ | START_DT | END_DT | DELAY | MM |
|-----|----------------------|----------------------|-------|----|
| 1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z | 30 | 1 |
| 1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z | 28 | 2 |
| 1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z | 5 | 3 |
| 2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z | 30 | 5 |
| 2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z | 30 | 6 |
| 2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z | 6 | 7 |
Upvotes: 2