Home » SQL & PL/SQL » SQL & PL/SQL » Help Needed in Calculation Logic function (oracle 19c)
Help Needed in Calculation Logic function [message #690212] Mon, 09 December 2024 08:08 Go to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
Hi, I need to create a function which takes two parameters as StartDate and StartDate+N as EndDate. N can be any number.
I need to calculate the difference b/w these two dates by excluding weekends. Any sample queries will be appreciatble
Re: Help Needed in Calculation Logic function [message #690213 is a reply to message #690212] Mon, 09 December 2024 08:39 Go to previous messageGo to next message
Michel Cadot
Messages: 68728
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator

If N is not big you can use brute force SQL:
SQL> create or replace function nbwdays (start_date date, nbdays integer) return integer
  2  is
  3    nbwdays integer;
  4  begin
  5    select count(*) into nbwdays
  6    from (select start_date+level-1 dt from dual connect by level <= nbdays)
  7    where to_char(dt,'Dy','NLS_DATE_LANGUAGE=AMERICAN') not in ('Sat','Sun');
  8    return nbwdays;
  9  end;
 10  /

Function created.

SQL> select nbwdays(sysdate, 10) from dual;
NBWDAYS(SYSDATE,10)
-------------------
                  8

1 row selected.

SQL> select nbwdays(sysdate, 20) from dual;
NBWDAYS(SYSDATE,20)
-------------------
                 15

1 row selected.
This assumes your week-end days are Saturday and Sunday otherwise change the values in the NOT IN expression.

[Updated on: Mon, 09 December 2024 08:40]

Report message to a moderator

Re: Help Needed in Calculation Logic function [message #690214 is a reply to message #690213] Mon, 09 December 2024 09:30 Go to previous messageGo to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
Thanks Mike for the reply and sorry for not explaining the requirement clear.

N- Cannot be negative.

start time and end time will be passed with 00:00:00 as hours. sorry for the confusion. all i need is to return end date by excluding the weekend and holidays.

For example, is startdate is 12/9/2024 and N = 6 then the function should return 12/17/2024 as return date (excluded weekend). so the function output will be a date. Please help me in this.
Re: Help Needed in Calculation Logic function [message #690216 is a reply to message #690214] Mon, 09 December 2024 11:03 Go to previous messageGo to next message
Michel Cadot
Messages: 68728
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator

SQL> create or replace function wday (start_date date, nbdays integer) return date
  2  is
  3    stdate date;
  4    endate date;
  5  begin
  6    -- Start date is next Monday if stat_date falls in a weekend (Saturday or Sunday)
  7    -- This uses the property that an ISO week (IW) starts a Monday
  8    stdate := greatest(start_date,trunc(start_date+2,'IW'));
  9    -- Add number of weeks as there are number of 5 days in "nbdays"
 10    endate := stdate + 7 * trunc(nbdays/5);
 11    -- Add rest of days
 12    endate := endate + mod(nbdays,5);
 13    -- If end date falls in a weekend go to next week
 14    endate := endate + case when trunc(endate+2,'IW') = trunc(endate,'IW') then 0 else 2 end;
 15    return endate;
 16  end;
 17  /

Function created.

SQL> select level, WDAY(sysdate,level) from dual connect by level <= 10;
     LEVEL WDAY(SYSDAT
---------- -----------
         1 10-DEC-2024
         2 11-DEC-2024
         3 12-DEC-2024
         4 13-DEC-2024
         5 16-DEC-2024
         6 17-DEC-2024
         7 18-DEC-2024
         8 19-DEC-2024
         9 20-DEC-2024
        10 23-DEC-2024

10 rows selected.

SQL> select level, WDAY(sysdate+level,1) from dual connect by level <= 10;
     LEVEL WDAY(SYSDAT
---------- -----------
         1 11-DEC-2024
         2 12-DEC-2024
         3 13-DEC-2024
         4 16-DEC-2024
         5 17-DEC-2024
         6 17-DEC-2024
         7 17-DEC-2024
         8 18-DEC-2024
         9 19-DEC-2024
        10 20-DEC-2024

10 rows selected.

SQL> select WDAY(to_date('12/9/2024','MM/DD/YYYY'),6) from dual;
WDAY(TO_DAT
-----------
17-DEC-2024

1 row selected.

[Updated on: Mon, 09 December 2024 11:08]

Report message to a moderator

Re: Help Needed in Calculation Logic function [message #690217 is a reply to message #690214] Mon, 09 December 2024 11:38 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3303
Registered: January 2010
Location: Connecticut, USA
Senior Member
Michel, performance will be relatively good even if N is large:

SQL> select  date '9999-12-31' - trunc(sysdate) max_possible_n
  2    from  dual
  3  /

MAX_POSSIBLE_N
--------------
       2912830

SQL> set timing on
SQL> select  count(*)
  2    from  dual
  3    where to_char(trunc(sysdate),'Dy','NLS_DATE_LANGUAGE=AMERICAN') not in ('Sat','Sun')
  4    connect by level <= 20
  5  /

  COUNT(*)
----------
        20

Elapsed: 00:00:00.65
SQL> select  count(*)
  2    from  dual
  3    where to_char(trunc(sysdate) + level - 1,'Dy','NLS_DATE_LANGUAGE=AMERICAN') not in ('Sat','Sun')
  4    connect by level <= 2912830
  5  /

  COUNT(*)
----------
   2080594

Elapsed: 00:00:04.80
SQL>
However question is if start_date + N is included or not? Michel's solution assumes it isn't included. For example:

SQL> select  count(*)
  2    from  dual
  3    where to_char(trunc(sysdate) + level - 1,'Dy','NLS_DATE_LANGUAGE=AMERICAN') not in ('Sat','Sun')
  4    connect by level <= 1
  5  /

  COUNT(*)
----------
         1

Elapsed: 00:00:00.00
SQL>
As you can see sysdate (12/9) is Monday, sysdate + 1 (12/10) is Tuesday, so count = 1 doesn't include Tuesday.

Anyway, if performance is a key (and I will assume start_date + N should be included):

trunc(start_dt,'iw')     - start date week start
trunc(start_dt + n,'iw') - end date (start date + n) week start

(trunc(start_dt + n,'iw') - trunc(start_dt,'iw')) / 7 full weeks between start date week start and end date week start
((trunc(sysdate + n,'iw') - trunc(sysdate,'iw')) / 7) * 5 weekdays in full weeks

least(trunc(sysdate) - trunc(sysdate,'iw'),4) - first week weekdays before start date
least(trunc(sysdate + n) - trunc(sysdate + n,'iw'),4) + 1  - last week weekdays before or equal to end date

Query:

select  ((trunc(sysdate + n,'iw') - trunc(sysdate,'iw')) / 7) * 5 - least(trunc(sysdate) - trunc(sysdate,'iw'),4) +
        least(trunc(sysdate + n) - trunc(sysdate + n,'iw'),4) + 1 weekdays
  from  dual
/
And a test:

SQL> select  ((trunc(sysdate + 20,'iw') - trunc(sysdate,'iw')) / 7) * 5 - least(trunc(sysdate) - trunc(sysdate,'iw'),4) +
  2          least(trunc(sysdate + 20) - trunc(sysdate + 20,'iw'),4) + 1 weekdays
  3    from  dual
  4  /

  WEEKDAYS
----------
        15

Elapsed: 00:00:00.01
SQL> select  ((trunc(sysdate + 2912830,'iw') - trunc(sysdate,'iw')) / 7) * 5 - least(trunc(sysdate) - trunc(sysdate,'iw'),4) +
  2          least(trunc(sysdate + 2912830) - trunc(sysdate + 2912830,'iw'),4) + 1 weekdays
  3    from  dual
  4  /

  WEEKDAYS
----------
   2080595

Elapsed: 00:00:00.00
SQL>
SY.
Re: Help Needed in Calculation Logic function [message #690218 is a reply to message #690216] Mon, 09 December 2024 12:27 Go to previous messageGo to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
thank you mike and is there way to exclude the holidays along with weekend?
Re: Help Needed in Calculation Logic function [message #690219 is a reply to message #690218] Mon, 09 December 2024 12:52 Go to previous messageGo to next message
Michel Cadot
Messages: 68728
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator

My latest function can be converted in a single formula (I splt it to add comments on how it works) but it is not possible to add holidays with it.
You first must have an holidays table and then use the brute force query also excluding the dates in this later table.

Re: Help Needed in Calculation Logic function [message #690220 is a reply to message #690219] Mon, 09 December 2024 21:30 Go to previous messageGo to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
Thank you Mike. with the example you gave, i  made my requirement. appreciated your responses.
Re: Help Needed in Calculation Logic function [message #690221 is a reply to message #690219] Thu, 12 December 2024 09:21 Go to previous messageGo to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
Hello Mike,

I created the holiday table which has Holiday_Date column and i want to exclude the holiday date and bring the net working day. for example,

if you pass the start date as 12/12/2024, N = 15, it will have to calculate the non working days b/w these days and add these up to get the next working day.  on this example it should return 1/4/25. because 12/24, 12/25, 1/1 is holiday. if i use  select max(WDAY(sysdate,level)) from dual connect by level <= 15 this gives the next working day without holiday dates checking. not sure how to brute force this checking with Holiday dates.   How to brute force it?

Also, i need to use the start date into the date logic.  
Re: Help Needed in Calculation Logic function [message #690222 is a reply to message #690219] Thu, 12 December 2024 09:58 Go to previous messageGo to next message
born2achieve
Messages: 20
Registered: July 2012
Junior Member
as another solution i tried, below the complete script. but this one is not fast.

CREATE TABLE  CALENDAR
(
    CALENDER_DATE    DATE PRIMARY KEY,
    WEEK_DAY      NUMBER (1) NOT NULL, 
    CREATED_DT      DATE DEFAULT SYSDATE
);


CREATE INDEX IX_CALENDAR_DAY_WEEK_DAY
    ON  CALENDAR (WEEK_DAY);
    
 

 INSERT INTO  CALENDAR (CALENDER_DATE, 
                      WEEK_DAY)
    WITH
         
        ALL_DTS
        AS
            (    SELECT DATE '2010-01-01' + LEVEL - 1     AS DT
                   FROM DUAL
             CONNECT BY DATE '2010-01-01' + LEVEL - 1 <= DATE '2110-01-01')
    SELECT D.DT,
         CASE WHEN  TO_CHAR (D.DT, 'fmDy', 'NLS_DATE_LANGUAGE=ENGLISH')

                                      IN ('Sat', 'Sun')

                                  THEN  0

                                  ELSE  1

                              END 
      FROM ALL_DTS  D;
	  
/

CREATE TABLE HOLIDAYS
(
    ID                 NUMBER (5) GENERATED AS IDENTITY PRIMARY KEY,
    HOLIDAY_DESC       VARCHAR (100),
    HOLIDAY_DATE       DATE not null, 
    CREATED_DT       DATE DEFAULT SYSDATE
);
 
 
CREATE INDEX IX_HOLIDAYS_HOLIDAY_DATE ON HOLIDAYS(HOLIDAY_DATE)

/

	  
-- View to combine holiday and workday table for easy map	  
 CREATE OR REPLACE FORCE VIEW V_CALENDAR
(
    CALENDER_DATE,
     WEEK_DAY
)
 
AS
    SELECT CALENDER_DATE,
           CASE WHEN H.HOLIDAY_DATE is not null THEN 0 ELSE  WEEK_DAY END    AS WEEK_DAY
      FROM CALENDAR  C
           LEFT JOIN HOLIDAYS H ON C.CALENDER_DATE = H.HOLIDAY_DATE
    UNION
    SELECT H.HOLIDAY_DATE, 0
      FROM  HOLIDAYS H
  


 
/

CREATE OR REPLACE FUNCTION Get_next_working_day (
    START_DATE   IN DATE,
    N_DAYS       IN PLS_INTEGER DEFAULT 1)
    RETURN DATE
AS
   
    NON_WORKING_DAYS   INT := 0;
    OUTPUT_DATE         DATE;
    COUNTER            INT := 0;
BEGIN
    SELECT COUNT (*)
      INTO NON_WORKING_DAYS
      FROM V_CALENDAR D
     WHERE     CALENDER_DATE BETWEEN TRUNC (START_DATE)
                                 AND TRUNC (START_DATE + N_DAYS)
           AND IS_WEEK_DAY = 0;

    SELECT TRUNC ((START_DATE + N_DAYS + NON_WORKING_DAYS))
      INTO OUTPUT_DATE
      FROM DUAL;
 
    BEGIN
        LOOP
            SELECT COUNT (1)
              INTO COUNTER
              FROM V_CALENDAR D
             WHERE     TRUNC (D.CALENDER_DATE) = TRUNC (OUTPUT_DATE)
                   AND WEEK_DAY = 0; 
            IF COUNTER = 0
            THEN
                EXIT;
            END IF;

            -- Increment counter
            COUNTER := COUNTER + 1;
            OUTPUT_DATE := FINAL_DATE + 1;
           
        END LOOP;
    END;

    RETURN OUTPUT_DATE;
END Get_next_working_day;
/
-- Update Query :

Update Reports set Report_Date = Get_next_working_day(calc_date, 15);

-- Reports table have 2500000 records and i need to call this cuntion to update the Report_date based on the Date in CALC_DATE column.

[Updated on: Thu, 12 December 2024 12:50]

Report message to a moderator

Re: Help Needed in Calculation Logic function [message #690226 is a reply to message #690222] Fri, 13 December 2024 12:42 Go to previous messageGo to next message
Michel Cadot
Messages: 68728
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator

SQL> CREATE TABLE HOLIDAYS
  2  (
  3      ID                 NUMBER (5) PRIMARY KEY,
  4      HOLIDAY_DESC       VARCHAR (100),
  5      HOLIDAY_DATE       DATE not null,
  6      CREATED_DT       DATE DEFAULT SYSDATE
  7  );

Table created.

SQL> insert into HOLIDAYS (ID, HOLIDAY_DATE) values (1, to_date('12/24/2024','MM/DD/YYYY'));

1 row created.

SQL> insert into HOLIDAYS (ID, HOLIDAY_DATE) values (2, to_date('12/25/2024','MM/DD/YYYY'));

1 row created.

SQL> insert into HOLIDAYS (ID, HOLIDAY_DATE) values (3, to_date('1/1/2025','MM/DD/YYYY'));

1 row created.

SQL> commit;

Commit complete.

SQL> create or replace function wday (start_date date, nbdays integer) return date
  2  is
  3    wday date;
  4  begin
  5    with
  6      days as (
  7        select level rn,
  8               case
  9                 when to_char(start_date+level,'Dy','NLS_DATE_LANGUAGE=AMERICAN') in ('Sat','Sun') then null
 10                 when (start_date+level) in (select holiday_date from holidays) then null
 11                 else 'WDAY'
 12               end wdflag
 13        from dual
 14        connect by level <= 7*ceil(nbdays/5)+30
 15      ),
 16      wdays as (
 17        select rn,
 18               count(wdflag) over (order by rn) nbwdays
 19        from days
 20      )
 21    select start_date + rn into wday
 22    from wdays
 23    where nbwdays = nbdays;
 24    return wday;
 25  end;
 26  /

Function created.

SQL> select wday(to_date('12/12/2024','MM/DD/YYYY'), 15) from dual;
WDAY(TO_DAT
-----------
07-JAN-2025

1 row selected.
For me, the result is 1/7/25 not 1/4/25 which is Saturday.
Checking using the inner query:
SQL>   with
  2      days as (
  3        select level rn,
  4               case
  5                 when to_char(to_date('12/12/2024','MM/DD/YYYY')+level,'Dy','NLS_DATE_LANGUAGE=AMERICAN') in ('Sat','Sun') then null
  6                 when (to_date('12/12/2024','MM/DD/YYYY')+level) in (select holiday_date from holidays) then null
  7                 else 'WDAY'
  8               end wdflag
  9        from dual
 10        connect by level <= 7*ceil(15/5)+30
 11      )
 12  select rn,
 13         to_date('12/12/2024','MM/DD/YYYY') + rn curdate,
 14         wdflag,
 15         count(wdflag) over (order by rn) nbwdays
 16  from days
 17  /
        RN CURDATE     WDFL    NBWDAYS
---------- ----------- ---- ----------
         1 13-DEC-2024 WDAY          1
         2 14-DEC-2024               1
         3 15-DEC-2024               1
         4 16-DEC-2024 WDAY          2
         5 17-DEC-2024 WDAY          3
         6 18-DEC-2024 WDAY          4
         7 19-DEC-2024 WDAY          5
         8 20-DEC-2024 WDAY          6
         9 21-DEC-2024               6
        10 22-DEC-2024               6
        11 23-DEC-2024 WDAY          7
        12 24-DEC-2024               7
        13 25-DEC-2024               7
        14 26-DEC-2024 WDAY          8
        15 27-DEC-2024 WDAY          9
        16 28-DEC-2024               9
        17 29-DEC-2024               9
        18 30-DEC-2024 WDAY         10
        19 31-DEC-2024 WDAY         11
        20 01-JAN-2025              11
        21 02-JAN-2025 WDAY         12
        22 03-JAN-2025 WDAY         13
        23 04-JAN-2025              13
        24 05-JAN-2025              13
        25 06-JAN-2025 WDAY         14
        26 07-JAN-2025 WDAY         15
...
Explanation of the "7*ceil(nbdays/5)+30" formula:
7*ceil(nbdays/5) is the total number of days during the weeks of 5 working days
30 is an arbitrary max number of holiday days during the period (change it as you estimate).

Note that you don't need the function to update your rows, you can directly use the query inside the function in your UPDATE statement.

Also:

Quote:
CREATE INDEX IX_HOLIDAYS_HOLIDAY_DATE ON HOLIDAYS(HOLIDAY_DATE)

Better use a UNIQUE constraint, I doubt you want 2 same dates in the table (but who knows? Smile ).

[Updated on: Sat, 14 December 2024 01:08]

Report message to a moderator

Re: Help Needed in Calculation Logic function [message #690235 is a reply to message #690226] Sat, 14 December 2024 03:18 Go to previous message
Michel Cadot
Messages: 68728
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator

A fix in the function to handle the case when the searched working day is followed by non-working day(s) (line 17 and 23):
SQL> create or replace function wday (start_date date, nbdays integer) return date
  2  is
  3    wday date;
  4  begin
  5    with
  6      days as (
  7        select level rn,
  8               case
  9                 when to_char(start_date+level,'Dy','NLS_DATE_LANGUAGE=AMERICAN') in ('Sat','Sun') then null
 10                 when (start_date+level) in (select holiday_date from holidays) then null
 11                 else 'WDAY'
 12               end wdflag
 13        from dual
 14        connect by level <= 7*ceil(nbdays/5)+30
 15      ),
 16      wdays as (
 17        select rn, wdflag,
 18               count(wdflag) over (order by rn) nbwdays
 19        from days
 20      )
 21    select start_date + rn into wday
 22    from wdays
 23    where nbwdays = nbdays and wdflag is not null;
 24    return wday;
 25  end;
 26  /

Function created.
Previous Topic: Find out the column values based on primary key from one table
Next Topic: Row generator
Goto Forum:
  


Current Time: Tue Dec 24 13:02:56 CST 2024