Reputation: 11
I'm trying to find the Oracle SQL for finding the last business day of current month and also last business day of previous month. Both cases should consider the federal holidays calendar.
For Example:
I should technically get my output as 29th November since 28th is Thanksgiving holiday.
Upvotes: 1
Views: 8414
Reputation: 31648
Store those federal holidays in a holidays table as DATE
type and then
try something like this: Find the oldest ( MAX
) day in the last seven days of the month that is neither a Saturday or a Sunday nor a holiday mentioned in
the holidays table.
The assumptions here are that 1) not all seven days at the end of the month can all be holidays or weekend and 2) Saturday and Sundays are off. You may adjust the level
or where clause accordingly, depending on whether the above assumption should always hold true or not.
SELECT MAX(dt) AS last_working_day
FROM
(
SELECT last_day(SYSDATE) - level + 1 as dt
FROM dual CONNECT BY
level <= 7 -- the last seven days of the month
) WHERE TO_CHAR(dt,'DY', 'NLS_DATE_LANGUAGE = AMERICAN') NOT IN ('SAT','SUN')
AND dt NOT IN ( SELECT holiday from federal_holidays );
A much better approach would be to have a Calendar table with all the dates of the year and predefined column called isbusinessday
. Then the query would be much simpler.
SELECT MAX(dt)
FROM calendar
WHERE isbusinessday = 'Y'
AND TO_CHAR(dt,'YYYYMM') = TO_CHAR(SYSDATE,'YYYYMM');
Upvotes: 4
Reputation: 59436
Having a holiday table is working in general, however it requires some maintenance as certain holidays are moving. For example Thanksgiving Day is the 4th Thursday in November, i.e. it varies from November 22 to November 28.
You can also use the Oracle build-in Scheduler. Usually it is used to control the SCHEDULER JOBS
but I don't see any reason why it should not be used for something else.
First create a list of federal holidays, for example this:
BEGIN
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'CHRISTMAS', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=1225', comments => 'December 25');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'COLUMBUS_DAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=OCT;BYDAY=2 MON', comments => '2nd Monday in October');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'INDEPENDENCE_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0704', comments => 'July 4');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'MARTIN_LUTHER_KING_DAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=JAN;BYDAY=3 MON', comments => '3rd Monday in January');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'MEMORIAL_DAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=MAY;BYDAY=-1 MON', comments => 'Last Monday of May');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'NEW_YEARS_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0101', comments => 'January 1');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'THANKSGIVING', repeat_interval => 'FREQ=MONTHLY;BYMONTH=NOV;BYDAY=4 THU', comments => '4th Thursday in November');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'WASHINGTONS_BIRTHDAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=FEB;BYDAY=3 MON', comments => '3rd Monday in February');
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'WEEKEND', repeat_interval => 'FREQ=DAILY;INTERVAL=1;BYDAY=SAT,SUN');
-- Combined schedule for all federal holidays
DBMS_SCHEDULER.CREATE_SCHEDULE(schedule_name => 'FEDERAL_HOLIDAYS', repeat_interval => 'FREQ=DAILY;INTERSECT=CHRISTMAS,INDEPENDENCE_DAY,MARTIN_LUTHER_KING_DAY,MEMORIAL_DAY,NEW_YEARS_DAY,THANKSGIVING,WASHINGTONS_BIRTHDAY');
END;
/
Have a look at Calendaring Syntax to see how repeat_interval
needs to be specified.
Then you can use procedure DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING
to get your date:
CREATE OR REPLACE FUNCTION LAST_BUSINESS_DAY(today IN TIMESTAMP DEFAULT SYSTIMESTAMP) RETURN TIMESTAMP AS
return_date_after TIMESTAMP := TRUNC(today);
next_run_date TIMESTAMP;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=DAILY;INTERVAL=1;EXCLUDE=FEDERAL_HOLIDAYS,WEEKEND', NULL, return_date_after, next_run_date);
EXIT WHEN next_run_date >= LAST_DAY(TRUNC(today));
return_date_after := next_run_date;
END LOOP;
RETURN return_date_after;
END LAST_BUSINESS_DAY;
Upvotes: 1