Business days calculation (by including time portion) between start date and end date by excluding weekends and custom/business specific holidays - oracle

Could you please help me out how to calculate the no. of business days between start date and end date by excluding weekends & business specific holidays. Let's say if we take start date, end date and custom holiday table with list of holiday dates as mentioned below.
Start date (mm/dd/yyyy hh24:mi:ss) : '10/15/2012 08:00:00'
End date (mm/dd/yyyy hh24:mi:ss) : '10/23/2012 10:30:00'
holiday_table.business_holiday_date values (mm/dd/yyyy):
10/17/2012
10/19/2012
10/22/2012
Need to calculate business days by taking into consideration of the time portion of dates in the calculation (so can expect business days with fractions as well ex. 1.25, 3.7 etc)
Appreciate if you can help me on this ASAP.
-James

Try:
SELECT(end_date - start_date) -
(select count(*)
from holiday_table
where business_holiday_date >= start_date
and business_holiday_date <= end_date)
FROM dual
Here is a fiddle

I have a function that use only 2 dates but you can improve it. Here you go...
First check how many days you got in the holiday table, excluding weekend days.
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;

Related

count Sunday in plsql between two dates :date1 and :date2

I know there is lot of work for this on forums but i tried many things but get error please I have two parameters in oracle reports :date1 and :date2 I want to check sunday and then return gives me how many Sundays in these two dates
function SUNDAY_CFormula return NUMBER is
start_date DATE := :DATE1;
end_date DATE := :DATE2;
A NUMBER;
begin
SELECT Count(*)
FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday')INTO A
FROM DUAL;
CONNECT BY LEVEL <= end_date - start_date + 1)
WHERE A IN ( 'sunday' );
RETURN A;
end;
You could rewrite your function like below. It is safer to add 'nls_date_language = english' clause in your to_char function in order to make your function independent from your default environnment settings.
create or replace
function SUNDAY_CFormula (DATE1 date, DATE2 date) return NUMBER is
start_date DATE := DATE1;
end_date DATE := DATE2;
A NUMBER;
begin
SELECT Count(*) INTO A
FROM (
SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday', 'nls_date_language = english') A
FROM DUAL
CONNECT BY LEVEL <= end_date - start_date + 1
) t
WHERE t.A IN ( 'sunday' );
RETURN A;
end;
/
You could even use below version to make your function more flexible about the two dates it takes as parameters, no matter if date1 is greater or less than date2.
create or replace
function SUNDAY_CFormula (DATE1 date, DATE2 date) return NUMBER is
start_date DATE := DATE1;
end_date DATE := DATE2;
A NUMBER;
begin
SELECT Count(*) INTO A
FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday', 'nls_date_language = english') A
FROM DUAL
CONNECT BY LEVEL <= greatest(end_date, start_date) - least(end_date, start_date) + 1
) t
WHERE t.A IN ( 'sunday' );
RETURN A;
end;
/
As an alternative. I always try creating formulas for date range process rather than "iterating", I just do not like generating data to just throw it away. Yes, it is sometimes necessary, but not in this case. The following will accomplish what you want:
create or replace
function sunday_calc ( date1_in date
, date2_in date
, sun_in varchar2 default 'sun'
)
return number
is
sun_count integer;
begin
with date_range( start_date, end_date) as
( select trunc(least(date1_in,date2_in))
, trunc(greatest(date1_in,date2_in))
from dual
)
select floor((trunc(end_date) - trunc(next_day(start_date-1,sun_in))/7)) + 1
into sun_count
from date_range;
return sun_count;
end sunday_calc;
Note: Unfortunately the next_day function does not accept a NLS_DATE_LANGUAGE parameter, so I created a substitute. Sun_in parameter: include the name in target language corresponding to English day 'Sunday'
Curious about this compared to the function by #MDO I ran some tests on each. And they produced the same result; Except in some instances where the start date was greater that the end date there was a difference of 1. Comparing to actual calendar the formula was correct (see fiddle). But why, MDO's logic seems completely sound. At that point I just had to know why. Took awhile but there is a slight bug in her/his code. Turns out when the start date is greater then the end date their routine actually began looking at dates for the greatest date and moved forward. Thus changing the period look at the the greater of the dates to that date plus number of days. This is corrected by applying the least function to the "Select to_char(start_date...", results in:
create or replace
function sunday_cformula_r (date1 date, date2 date) return number is
start_date date := date1;
end_date date := date2;
a number;
begin
select count(*) into a
from (select to_char(least(end_date, start_date) + ( level - 1 ), 'fmday', 'nls_date_language = english') a
from dual
connect by level <= greatest(end_date, start_date) - least(end_date, start_date) + 1
) t
where t.a in ( 'sunday' );
return a;
end;

Stored procedure to get one year of data splitted per months

I am relatively new to Oracle so i'd really appreciate some help.
I have a query like:
SELECT * FROM reservations WHERE date between (date1) and (date2).
What I need is to get all the reservations within the interval : today's date and today's date -1 year, so basically 1 year of history.
I want to run the above query with interval of 1 months, and export the query set to excel.
I need some help in the logic of the loop (create a stored procedure or function), as i will think later for the export to excel.
This will give all records from 1 year back to today:
SELECT * FROM reservations
WHERE date >= trunc( sysdate ) - interval '1' year
AND date < trunc( sysdate ) + interval '1' day
I want to run the above query with interval of 1 months,
I understand that you want to run this query 12 times, each time for another monthly period. If yes, then run this query 12 times changing the parameter X (within SELECT 1 As X FROM dual subquery), beginning from 12 to 1 (or 1 to 12):
SELECT * FROM reservations
CROSS JOIN (
SELECT 1 As X FROM dual
) x
WHERE date >= trunc( sysdate ) - x * interval '1' month
AND date < trunc( sysdate ) + interval '1' day - ( x - 1 ) * interval '1' month
Here is the procedure to create the queries month-wise starting from sysdate .
create or replace procedure monthwise_query
as
t_date DATE := TRUNC(SYSDATE) ;
c_date DATE ;
BEGIN
FOR i in 1..12
LOOP
c_date := t_date;
dbms_output.put_line('select * from reservations where rev_date between to_date('''||c_date||''',''DD-MON-YY'') and (select add_months(to_date('''||c_date||''',''DD-MON-YY''),-1) from dual);');
select add_months(c_date,-1)
into t_date
from dual;
END LOOP;
END;
- It can be extended with UTIL_FILE utility inside this procedure to write each query result to single/multiple files.
- SPOOL from SQL*plus for each query will be easy
- UNIX script to loop through each monthwise-query to load to file also possible

Return records from todays month + 6

how can i return records sales from todays + 6 months.
Example:
Today´s month: May-2018.
Today´s month + 6: Nov-2018
So, i want a function to retrieve records from Table_Date, from November (1 to 30).
Thanks.
Use the TRUNC and ADD_MONTHS functions:
SELECT *
FROM sales
WHERE SalesDate >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), 6 )
AND SalesDate < ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), 7 );
You have SQL server and oracle, it cant be both.
This is for SQL Server (though logic would work in ORACLE, but syntax may not)
This is to get 6 months from today
SELECT DATEADD(MM, 6, GETDATE())
Added extra code here for more details after looking at question more:
I am using variables to do the calculations to make it easier to understand but you could do it all inline (but harder to read).
This script gets a date 6 months from now, takes that month and finds the first day of that month, and then the last day of that month so you can use those 2 dates in a where clause logic.
DECLARE #NewMonth AS VARCHAR(10)
DECLARE #NewYear AS VARCHAR(10) -- in case adding months goes to next year
SELECT #NewMonth = DATEPART(MONTH, DATEADD(MM, 6, GETDATE())),
#NewYear = DATEPART(YEAR, DATEADD(MM, 6, GETDATE()))
DECLARE #FirstOfMonth AS DATE = #NewMonth + '/1/' + #NewYear
-- for start of month check we can just hard code one in it, for end of month we cant because we dont know how many days are in it
SELECT #FirstOfMonth, DATEADD(DAY, -1, DATEADD(MONTH, 1, #FirstOfMonth)) AS LastDayOfMontbh

Get First Day Of Week From Week Number

In Oracle, is there a straightforward way to get the first day of the week given a week number?
For example, today's date is 12/4/2012. If I run:
select to_char(sysdate,'WW') from dual;
It returns 49 for the week number.
What I would like to do is somehow return 12/2/2012 for the first day...given week 49 (assuming Sunday as first day of the week).
Any ideas? Thanks in advance for any help!
try this:
select next_day(max(d), 'sun') requested_sun
from (select to_date('01-01-2012', 'dd-mm-yyyy') + (rownum-1) d from dual connect by level <= 366)
where to_char(d, 'ww') = 49-1;
just set your year to_date('01-01-2012' and week number-1 49-1 as applicable.
the sunday in the 49th week of 2008?
SQL> select next_day(max(d), 'sun') requested_sun
2 from (select to_date('01-01-2008', 'dd-mm-yyyy') + (rownum-1) d from dual connect by level <= 366)
3 where to_char(d, 'ww') = 49-1;
REQUESTED
---------
07-DEC-08
and 2012
SQL> select next_day(max(d), 'sun') requested_sun
2 from (select to_date('01-01-2012', 'dd-mm-yyyy') + (rownum-1) d from dual connect by level <= 366)
3 where to_char(d, 'ww') = 49-1;
REQUESTED
---------
02-DEC-12
Try this,
select
next_day(trunc(to_date(in_year,'yyyy'),'yyyy') -1,'Mon') + (7 * (in_week - 1))
from dual;
If you have the date, not just the week number, you can try this:
Get the day number of the week of your date with: to_char(theDate, 'D')
substract that number from your date plus 1, and you'll get the Sunday of that week.
Add 7 and you'll get the date of end of the week(Saturday).
Like this:
SELECT theDate - to_char(theDate, 'D') + 1 as BeginOfWeek,
theDate,
theDate - to_char(theDate, 'D') + 7 as EndOfWeek
FROM TableName
I can't comment on questions yet, so I'll add another one. But this is based on #Dazzals answer.
His solution doesn't work for week one and for ISO-weeks. Also it doesn't work, if the first day of the week is not sunday, which can be controlled by NLS_SETTINGS.
This one does:
SELECT MIN(D)
FROM (SELECT TO_DATE('01-01-2013', 'dd-mm-yyyy') + (ROWNUM-10) D, ROWNUM R
FROM DUAL
CONNECT BY LEVEL <= 376)
WHERE TO_CHAR(D,'IYYYIW') = '201301'
Because we are spanning more than one year, we need to check the year too.
Using the trunc function #Justin used, I think this is what you want:
select trunc(to_date('2012-01-01', 'YYYY-MM-DD') + (49 - 1) * 7, 'WW') from dual;
I ended up doing this:
function getFirstDayOfWeek(y in binary_integer, w in binary_integer) return date
is
td date;
begin
td:=TO_DATE(TO_CHAR(y)||'0101', 'YYYYMMDD');
for c in 0..52
loop
if TO_NUMBER(TO_CHAR(td, 'IW'))=w then
return TRUNC(td, 'IW');
end if;
td:=td+7;
end loop;
return null;
end;

Oracle date difference to get number of years

Is there a way to calculate the number of years between dates. Not sure how to do this while accounting for leap and what not. Is it possible to do an IF statement maybe in the SELECT?
Thanks
I'd use months_between, possibly combined with floor:
select floor(months_between(date '2012-10-10', date '2011-10-10') /12) from dual;
select floor(months_between(date '2012-10-9' , date '2011-10-10') /12) from dual;
floor makes sure you get down-rounded years. If you want the fractional parts, you obviously want to not use floor.
If you just want the difference in years, there's:
SELECT EXTRACT(YEAR FROM date1) - EXTRACT(YEAR FROM date2) FROM mytable
Or do you want fractional years as well?
SELECT (date1 - date2) / 365.242199 FROM mytable
365.242199 is 1 year in days, according to Google.
I had to implement a year diff function which works similarly to sybase datediff. In that case the real year difference is counted, not the rounded day difference. So if there are two dates separated by one day, the year difference can be 1 (see select datediff(year, '20141231', '20150101')).
If the year diff has to be counted this way then use:
EXTRACT(YEAR FROM date_to) - EXTRACT(YEAR FROM date_from)
Just for the log the (almost) complete datediff function:
CREATE OR REPLACE FUNCTION datediff (datepart IN VARCHAR2, date_from IN DATE, date_to IN DATE)
RETURN NUMBER
AS
diff NUMBER;
BEGIN
diff := CASE datepart
WHEN 'day' THEN TRUNC(date_to,'DD') - TRUNC(date_from, 'DD')
WHEN 'week' THEN (TRUNC(date_to,'DAY') - TRUNC(date_from, 'DAY')) / 7
WHEN 'month' THEN MONTHS_BETWEEN(TRUNC(date_to, 'MONTH'), TRUNC(date_from, 'MONTH'))
WHEN 'year' THEN EXTRACT(YEAR FROM date_to) - EXTRACT(YEAR FROM date_from)
END;
RETURN diff;
END;";
Need to find difference in year, if leap year the a year is of 366 days.
I dont work in oracle much, please make this better.
Here is how I did:
SELECT CASE
WHEN ( (fromisleapyear = 'Y') AND (frommonth < 3))
OR ( (toisleapyear = 'Y') AND (tomonth > 2)) THEN
datedif / 366
ELSE
datedif / 365
END
yeardifference
FROM (SELECT datedif,
frommonth,
tomonth,
CASE
WHEN ( (MOD (fromyear, 4) = 0)
AND (MOD (fromyear, 100) <> 0)
OR (MOD (fromyear, 400) = 0)) THEN
'Y'
END
fromisleapyear,
CASE
WHEN ( (MOD (toyear, 4) = 0) AND (MOD (toyear, 100) <> 0)
OR (MOD (toyear, 400) = 0)) THEN
'Y'
END
toisleapyear
FROM (SELECT (:todate - :fromdate) AS datedif,
TO_CHAR (:fromdate, 'YYYY') AS fromyear,
TO_CHAR (:fromdate, 'MM') AS frommonth,
TO_CHAR (:todate, 'YYYY') AS toyear,
TO_CHAR (:todate, 'MM') AS tomonth
FROM DUAL))
For Oracle SQL Developer I was able to calculate the difference in years using the below line of SQL. This was to get Years that were within 0 to 10 years difference. You can do a case like shown in some of the other responses to handle your ifs as well. Happy Coding!
TRUNC((MONTHS_BETWEEN(<DATE_ONE>, <DATE_TWO>) * 31) / 365) > 0 and TRUNC((MONTHS_BETWEEN(<DATE_ONE>, <DATE_TWO>) * 31) / 365) < 10

Resources