Skip to Main Content
  • Questions
  • How to get the Number of Day between two dates, subject to some conditions

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, vinesh.

Asked: August 29, 2017 - 2:00 pm UTC

Last updated: September 12, 2017 - 11:21 am UTC

Version: oracle 11g

Viewed 1000+ times

You Asked

Team,

Please find below the Create Table stmt
CREATE TABLE ST_TA_STOP_INTEREST(LOAN_CODE NUMBER, TRANSACTION_DATE DATE,EVENT_ID VARCHAR2(5));


Please find below the insert stmts for the above created table:

SET DEFINE OFF;
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('07/29/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('08/05/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'RST');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('08/16/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('08/20/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'RST');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('08/26/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('08/31/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'RST');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('09/03/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('09/10/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'RST');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('09/15/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('09/30/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'RST');
Insert into ST_TA_STOP_INTEREST
   (LOAN_CODE, TRANSACTION_DATE, EVENT_ID)
 Values
   (1001, TO_DATE('10/10/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), 'STP');
COMMIT;


In the given ST_TA_STOP_INTEREST table loan_Code indicates the loan code of the customer. Here we have only one loan code 1001 . Transaction_date indicates the date the transaction has been posted.Event_id indicates the id of the event.

In the event_id column STP STANDS for "STOP Interest" and RST indicates "START INTEREST".


In other words if the record has STP as event_id then , in that scenario Interest calculation has to be stopped till RST record(even_id has to be RST).

So here if we see the data in the table , order by the transaction_date column, on 07/29/2017 we see record with STP as event_id and the next record in the table is with event_id RST and transaction_date as 08/05/2017.

In other words Interest calculation has to be stopped from 07/29/2017 to 08/04/2017 and interest calculation has to started from 08/05/2017 to 08/16/2017(as per the next record in the ST_TA_STOP_INTEREST table).


The inserts in the table for any loan_code will start with a STP as event_id , but the last record for that loan_code can have event_id as STP or RST.

Moreover each STP has to be followed by RST and viceversa.

If we have last record for that loan_code ,withe event_id as STP then interest calculation has to be stopped from that date till future date of RST as event_id(Currently future date may not be available as in this table, but the last record in on 10/10/2017 with with event_id as STP).

If we have last records for that loan_Code ,with event_id as RST then interest calculation has to be started from that date till future date of STP as event_id(Currently future date may not be there ).

Now I would like to calculate the count of overall days for which interest has to be calculated in the given date range .

The date range can be anything from 01/01/2017 to 12/31/2017.

In other words, the difference would be calucated as: ((Difference between the given date range)- (the duration in which the interest calcultion has to be stopped(count of all days between STP and RST in the given order of the Transaction date)))

For ex:

If we give the date range as 07/29/2017 to 08/04/2017 then the diffenece days has to be 0, because the interest calculation has been stopped during those days duration.

If we give the date range as 07/25/2017 and 07/28/2017 then the difference days has to be 3, since there is no stoppage of interest calculation during those days.

If we give the date range as 10/05/2017 and 10/19/2017 then the difference days has to be 4,since from 10/05/2017 to 10/09/2017 the interest is in start state and the
difference days is 4 and from 10/10/2017 it has to be stopped.

If there are no records for given date ranges then , we need to get the difference between those days.

Preferably we are looking for a function , that should handle this situation

Thanking you in anticipation.

and Chris said...

Here's one way to do it:

- For each transaction find the date of the next one using lead
- Generate the dates in your range
- Outer join these to the transactions where the dates are between the start and the next
- Then count the RST events or those that are null (assuming that dates before the first transaction are active)

Put it all together and you get:

var st varchar2(10);
var en varchar2(10);

exec :st := '2017-07-25';
exec :en := '2017-10-25';
with ranges as (
  select s.*, 
         lead( TRANSACTION_DATE ) 
           over (order by TRANSACTION_DATE) next_trans_date
  from   ST_TA_STOP_INTEREST s
), dates as (
  select to_date(:st, 'yyyy-mm-dd')+rownum-1 dt from dual
  connect by level <= to_date(:en, 'yyyy-mm-dd') - to_date(:st, 'yyyy-mm-dd') + 1
), interest_dates as (
  select d.dt, r.*, 
         min(d.dt) over () mn_dt, 
         max(d.dt) over () mx_dt
  from   dates d
  left join ranges r
  on     d.dt >= r.TRANSACTION_DATE 
  and    ( d.dt < r.next_trans_date or r.next_trans_date is null)
)
  select count(*)
  from   interest_dates
  where  event_id = 'RST' or event_id is null;

COUNT(*)  
39  

Rating

  (3 ratings)

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

Comments

Extension

Racer I., August 30, 2017 - 8:11 am UTC

Hi,

WITH
Config AS (
SELECT TO_DATE('2017-07-25', 'YYYY-MM-DD') dfrom,
TO_DATE('2017-10-25', 'YYYY-MM-DD') dto,
'MONTH' ptype, 1000 principal, 0.01 irate
FROM DUAL),
AllDates AS (
SELECT dfrom + ROWNUM - 500 dday, TRUNC(dfrom + ROWNUM - 500, ptype) period
FROM Config
CONNECT BY ROWNUM <= dto - dfrom + 1000),
IStops AS (
SELECT s.*,
LEAD(TRANSACTION_DATE) OVER (ORDER BY TRANSACTION_DATE) nextd
FROM ST_TA_STOP_INTEREST s),
AllTimes AS (
SELECT d.dday, d.period, CASE WHEN d.dday BETWEEN c.dfrom AND c.dto AND Event_ID IS NULL THEN 1 ELSE 0 END iapply
FROM AllDates d
CROSS JOIN Config c
LEFT JOIN IStops r
ON ('STP' = r.EVENT_ID AND d.dday BETWEEN r.TRANSACTION_DATE AND NVL(r.nextd, d.dday))),
PTimes AS (
SELECT period, COUNT(*) pdays, SUM(iapply) idays
FROM AllTimes
GROUP BY period),
PRates AS (
SELECT p.period, p.pdays, p.idays, c.principal, (c.irate * (p.idays / p.pdays)) eirate
FROM Config c
CROSS JOIN PTimes p
WHERE p.period BETWEEN TRUNC(c.dfrom, c.ptype) AND TRUNC(c.dto, c.ptype))
SELECT period, pdays, idays, principal, trunc(eirate * 100, 2) irate_p,
principal * EXP(SUM(LN(1 + eirate)) OVER (ORDER BY Period)) balance
FROM PRates
ORDER BY Period

vinesh, August 30, 2017 - 12:35 pm UTC

Thanks Chris for your immediate reply.

How ever i have a small problem . As per the table given we could see only one loan_code . In this case it's working as expected.

But in case if i have multiple loan_Codes, it seems to be failing.

The changes i have made in the function is:

select s.*,
lead( TRANSACTION_DATE )
over (order by TRANSACTION_DATE) next_trans_date
from ST_TA_STOP_INTEREST s
where loan_Code= p_loan_Code. (P_loan_Code is a parameter to the function).

let me provide you with new set of data for your perusal.

SET DEFINE OFF;
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90005, 'RST', TO_DATE('07/03/2017 02:11:32', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90005, 'STP', TO_DATE('07/03/2017 02:11:06', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90004, 'RST', TO_DATE('06/25/2017 12:16:34', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90004, 'STP', TO_DATE('06/20/2017 12:16:48', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90003, 'RST', TO_DATE('09/29/2017 03:21:37', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90003, 'STP', TO_DATE('09/29/2017 03:20:47', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90003, 'STP', TO_DATE('10/04/2017 13:56:03', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90002, 'RST', TO_DATE('08/22/2017 06:56:54', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90002, 'STP', TO_DATE('08/22/2017 06:43:19', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90001, 'RST', TO_DATE('08/25/2017 02:09:22', 'MM/DD/YYYY HH24:MI:SS'));
Insert into ST_TA_STOP_INTEREST
(LOAN_CODE, EVENT_ID, TRANSACTION_DATE)
Values
(90001, 'STP', TO_DATE('08/20/2017 02:01:52', 'MM/DD/YYYY HH24:MI:SS'));

commit;
The dates can be truncated to have only date and no timestamp.



REgards,
Vinesh



Chris Saxon
August 31, 2017 - 10:15 am UTC

If you want to split out your rows by loan_code (or whatever), you need a to add a partition by clause to the analytic functions (lead, min & max).

Connor explains at:


How to get the Number of Day between two dates, subject to some conditions

vinesh, September 08, 2017 - 12:15 pm UTC

Hi Chris,

You may Please consider this as a review or followup to my original post.

Though there would be slight change in the table data, but the table structure should be the same.

The variation in the data could be that in the present inset stmts provided in the original post ,we could see only one loan code . but let's consider the case where in the loan codes could be more than one, as per the sample data provided in the Initial review for your code snippet.

Based on the code snippet you have provided, we have made small modification to meet our requirements i.e. we have made it to work for the loan code provided , and it's working as expected except in the below scenario.

let's have a glance at the modified code.

</>
CREATE OR REPLACE FUNCTION fnc_calc_diff_days (
st VARCHAR2,
en VARCHAR2,
ploan_code NUMBER
)
RETURN NUMBER
AS
total_days NUMBER;
BEGIN
WITH ranges AS
(SELECT s.*,
LEAD (transaction_date) OVER (ORDER BY transaction_date)
next_trans_date
FROM st_ta_stop_interest s
WHERE loan_code = ploan_code),
dates AS
(SELECT TO_DATE (st, 'yyyy-mm-dd') + ROWNUM - 1 dt
FROM DUAL
CONNECT BY LEVEL <=
TO_DATE (en, 'yyyy-mm-dd')
- TO_DATE (st, 'yyyy-mm-dd')
+ 1),
interest_dates AS
(SELECT d.dt, r.*, MIN (d.dt) OVER () mn_dt, MAX (d.dt) OVER () mx_dt
FROM dates d LEFT JOIN ranges r
ON d.dt >= TRUNC (r.transaction_date)
AND ( d.dt < TRUNC (r.next_trans_date)
OR r.next_trans_date IS NULL
)
)
SELECT COUNT (*)
INTO total_days
FROM interest_dates
WHERE event_id = 'RST' OR event_id IS NULL;

RETURN total_days;
END;


</>

The issue is when we give the date ranges outside the STP AND RST range we are getting one day more as per the normal difference days.In other cases it's working fine.

for ex if we give date ranges as 07/20/2017 and 07/25/2017 for loan_code 1001, then we need to get the difference days as 5 days, but we are getting the difference as 6 days.

Any Help in this situation is most welcome.

Thanking you in Anticipation.



Chris Saxon
September 12, 2017 - 11:21 am UTC

That's because you're generating 6 days in your connect by date:

var st varchar2(10);
var en varchar2(10);
exec :st := '2017-07-20';
exec :en := '2017-07-25';
SELECT TO_DATE (:st, 'yyyy-mm-dd') + ROWNUM - 1 dt 
FROM DUAL 
CONNECT BY LEVEL <= TO_DATE (:en, 'yyyy-mm-dd') - TO_DATE (:st, 'yyyy-mm-dd') + 1;

DT                    
20-JUL-2017 00:00:00  
21-JUL-2017 00:00:00  
22-JUL-2017 00:00:00  
23-JUL-2017 00:00:00  
24-JUL-2017 00:00:00  
25-JUL-2017 00:00:00


So when you say from 20 Jul -> 25 Jul, do you mean:

- Starting from 20 July up to but NOT incuding 25 July (5 days)
- Starting from 20 July up to AND incuding 25 July (6 days)

?

If it's the first, remove the +1 from your connect by:

CONNECT BY LEVEL <= TO_DATE (:en, 'yyyy-mm-dd') - TO_DATE (:st, 'yyyy-mm-dd')

More to Explore

Analytics

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