I need to write an sql query that returns the number of Working days (Monday - Friday) between two given dates.
I was wondering what would be the most efficient way
This is how i do it, assuming you already got a calendar table with acolumn which indicates if a day is working day or not: Add a new column to your calendar table like workday_num and populate it once with a running number using
sum(case when workingday then 1 else 0 end)
over (order by calendardate rows unbounded preceding)
Now it's two joins to your calendar and a simple difference of the workday_nums of p_start_date and p_end_date.
Thats so simple :
SQL> Select count(*)
2 from ( select rownum rnum
3 from all_objects
4 where rownum <= to_date('18-dec-2009','dd-mon-yyyy') -
to_date('16-nov-2009')+1 )
5 where to_char( to_date('16-nov-2009','dd-mon-yyyy')+rnum-1, 'DY' )
6 not in ( 'SAT', 'SUN' )
COUNT(*)
----------
25
SQL> Select to_char( to_date('16-nov-2009','dd-mon-yyyy')+rnum-1, 'DY dd-mon-yyyy' )
2 from ( select rownum rnum
3 from all_objects
4 where rownum <= to_date('18-dec-2009','dd-mon-yyyy') - to_date('16-nov-2009')+1 )
5 where to_char( to_date('16-nov-2009','dd-mon-yyyy')+rnum-1, 'DY' )
6 not in ( 'SAT', 'SUN' )
DAY_DATE
---------------
MON 16-nov-2009
TUE 17-nov-2009
WED 18-nov-2009
THU 19-nov-2009
FRI 20-nov-2009
MON 23-nov-2009
TUE 24-nov-2009
WED 25-nov-2009
THU 26-nov-2009
FRI 27-nov-2009
MON 30-nov-2009
TUE 01-dec-2009
WED 02-dec-2009
THU 03-dec-2009
FRI 04-dec-2009
MON 07-dec-2009
TUE 08-dec-2009
WED 09-dec-2009
THU 10-dec-2009
FRI 11-dec-2009
MON 14-dec-2009
TUE 15-dec-2009
WED 16-dec-2009
THU 17-dec-2009
FRI 18-dec-2009
25 rows selected.
It can be achieved by:
select SUM(decode ( to_CHAR((sysdate-ROWNUM),'DY'),'SUN',0,'SAT',0,1)) from all_objects where rownum < sysdate - (sysdate -9)
an easy way to calculate to number of weekdays between 2 dates is :
SELECT
date1,
date2,
((date2-date1)-2*FLOOR((date2-date1)/7)-DECODE(SIGN(TO_CHAR(date2,'D')-
TO_CHAR(date1,'D')),-1,2,0)+DECODE(TO_CHAR(date1,'D'),7,1,0)-
DECODE(TO_CHAR(date2,'D'),7,1,0))*24 as WorkDays
FROM
tablename
ORDER BY date1,date2
Here is an example
with given_days(d) as(
select <<start_date>> + level - 1
from dual
connect by level < = (<<end_date>> - <<start_date>>) + 1
)
select count(*)
from given_days
where to_char(d, 'DY', 'NLS_DATE_LANGUAGE=english') not in ('SUN', 'SAT')
Demonstration
HR\XE> with given_days as(
2 select (to_date('&&1', 'dd.mm.yyyy') + level - 1) as g_day
3 from dual
4 connect by level < = (to_date('&2', 'dd.mm.yyyy') - to_date('&&1', 'dd.mm.yyyy')) + 1
5 )
6 select count(g_day) as cnt
7 from given_days
8 where to_char(g_day, 'DY', 'NLS_DATE_LANGUAGE=english') not in ('SUN', 'SAT');
Enter value for 1: 10.10.2012
old 2: select to_date('&&1', 'dd.mm.yyyy') + level - 1
new 2: select to_date('10.10.2012', 'dd.mm.yyyy') + level - 1
Enter value for 2: 17.10.2012
old 4: connect by level < = (to_date('&2', 'dd.mm.yyyy') - to_date('&&1', 'dd.mm.yyyy')) + 1
new 4: connect by level < = (to_date('17.10.2012', 'dd.mm.yyyy') - to_date('10.10.2012', 'dd.mm.yyyy')) + 1
cnt
----------
6
Here you go...
Get business days (MON to FRI) between the 2 dates and after that subtract the holiday days.
create or replace
FUNCTION calculate_business_days (p_start_date IN DATE, p_end_date IN DATE)
RETURN NUMBER IS
v_holidays NUMBER;
v_start_date DATE := TRUNC (p_start_date);
v_end_date DATE := TRUNC (p_end_date);
BEGIN
IF v_end_date >= v_start_date
THEN
SELECT COUNT (*)
INTO v_holidays
FROM holidays
WHERE day BETWEEN v_start_date AND v_end_date
AND day NOT IN (
SELECT hol.day
FROM holidays hol
WHERE MOD(TO_CHAR(hol.day, 'J'), 7) + 1 IN (6, 7)
);
RETURN GREATEST (NEXT_DAY (v_start_date, 'MON') - v_start_date - 2, 0)
+ ( ( NEXT_DAY (v_end_date, 'MON')
- NEXT_DAY (v_start_date, 'MON')
)
/ 7
)
* 5
- GREATEST (NEXT_DAY (v_end_date, 'MON') - v_end_date - 3, 0)
- v_holidays;
ELSE
RETURN NULL;
END IF;
END calculate_business_days;
After that you can test it out, like:
select
calculate_business_days('21-AUG-2013','28-AUG-2013') as business_days
from dual;