Number of fridays between two dates - sql

How do I find the number of fridays between two dates(including both the dates) using a select statement in oracle sql?

This will do it:
select ((next_day(date2-7,'FRI')-next_day(date-1,'FRI'))/7)+1 as num_fridays
from data
Perhaps best if I break that down. The NEXT_DAY function returns the next day that is a (Friday in this case) after the date.
So to find the first Friday after d1 would be:
next_day( d1, 'FRI')
But if d1 is a Friday that would return the following Friday, so we adjust:
next_day( d1-1, 'FRI')
Similarly to find the last Friday up to and including d2 we do:
next_day( d1-7, 'FRI')
Subtracting the 2 gives a number of days: 0 if they are the same date, 7 if they a re a week apart and so on:
next_day( d1-7, 'FRI') - next_day( d1-1, 'FRI')
Convert to weeks:
(next_day( d1-7, 'FRI') - next_day( d1-1, 'FRI')) / 7
Finally, if they are the same date we get 0, but really there is 1 Friday, and so on so we add one:
((next_day( d1-7, 'FRI') - next_day( d1-1, 'FRI')) / 7) + 1

I have to throw in my two cents for using a calendar table. (It's a compulsion.)
select count(*) as num_fridays
from calendar
where day_of_week = 'Fri'
and cal_date between '2011-01-01' and '2011-02-17';
num_fridays
-----------
6
Dead simple to understand. Takes advantage of indexes.
Maybe I should start a 12-step group. Calendar Table Anonymous.

See:
Why should I consider using an auxiliary calendar table?
The article's code is specifically for SQL Server but the techniques are portable to most SQL platforms.
With a Calendar table in place your query could be as simple as
SELECT COUNT(*) AS friday_tally
FROM YourTable AS T1
INNER JOIN Calendar AS C1
ON C1.dt BETWEEN T1.start_date AND T1.end_date
WHERE C1.day_name = 'Friday'; -- could be a numeric code

select sum(case when trim(to_char(to_date('2009-01-01','YYYY-MM-DD')+rownum,'Day')) = 'Friday' then 1 else 0 end) number_of_fridays
from dual
connect by level <= to_date('&end_date','YYYY-MM-DD') - to_date('&start_date','YYYY-MM-DD')+1;
Original source - http://forums.oracle.com/forums/thread.jspa?messageID=3987357&tstart=0

Try modifying this one:
CREATE OR REPLACE FUNCTION F_WORKINGS_DAYS
(V_START_DATE IN DATE, V_END_DATE IN DATE)
RETURN NUMBER IS
DAY_COUNT NUMBER := 0;
CURR_DATE DATE;
BEGIN -- loop through and update
CURR_DATE := V_START_DATE;
WHILE CURR_DATE <= V_END_DATE
LOOP
IF TO_CHAR(CURR_DATE,'DY') NOT IN ('SAT','SUN') -- Change this bit to ignore all but Fridays
THEN DAY_COUNT := DAY_COUNT + 1;
END IF;
CURR_DATE := CURR_DATE + 1;
END LOOP;
RETURN DAY_COUNT;
END F_WORKINGS_DAYS;
/

SELECT (NEXT_DAY('31-MAY-2012','SUN')
-NEXT_DAY('04-MAR-2012','SUN'))/7 FROM DUAL

select ((DATEDIFF(dd,#a,#b)) + DATEPART(dw,(#a-6)))/7

Related

How do I convert a Week Number to From date of the week in oracle

Suppose I enter WeekNo: 14 the query should return the From Date: April 4th 2016, since the week 14 starts from 4th April to 10th April
select to_date('14','iw') FROM dual;
something like this ? (it work for current year) there discard data from another years
with dates as (select to_char(
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1
,'IW') we,
to_date('1.01.'||extract(year from sysdate),'dd.mm.yyyy' ) + level -1 da
from dual
connect by level <= 365 + 10 )
select * from (
select case
when -- we = null if number of week in jan > 1,2,3,4....
((to_number(we) > 40 )
and extract(year from sysdate) = extract(year from da)
and extract(month from da) = '01') or
-- we = null when current year < year of da
(extract(year from sysdate) != extract(year from da))
then
null
else we
end we,
da
from dates
)
where we = 14
and rownum = 1
Dealing with ISO-Weeks is not trivial, for example January, 1st 2016 is week 53 of 2015, see
select to_char(date '2016-01-01', 'iyyy-"W"iw') from dual;
So, providing only the week number without the (ISO-) year is ambiguous - although it is obvious as along as you are not around new-years date.
Some time ago I wrote this function to get the date from ISO-Week.
FUNCTION ISOWeekDate(week INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF week > 53 OR week < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;
Of course you can just select NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( week - 1 ) * 7;, however this would not be error-safe if somebody uses the wrong year.
If it is not an issue to append the year to the week you are looking for, you can also use this :
SELECT (TRUNC ( TO_DATE (SUBSTR ('201627', 1, 4) || '0131', 'YYYY'|| 'MMDD'), 'IYYY')
+ ( 7 * ( TO_NUMBER (SUBSTR ('201627', 5)) - 1)) ) AS iw_Monday
FROM dual
With in this example 201627 being the YYYYIW you are looking for.
It will return the date of the MONDAY of that week.
Found it on Oracle forums, there are a couple of other solutions there. I found this one to be the most elegant.
The advantage is that you do everything from the SELECT, and you don't need either a function or PL/SQL or a WHERE clause.
Disadvantages : you must append the year and specify your search week 2 times, unless you use a variable
Here is a simple and direct computation, taking advantage of various Oracle date functions. Since it compares to what Oracle already counts as ISO week etc., it shouldn't be subject to any of the difficulties other solutions correctly point to and address with additional code.
The "magic number" 14 in the formula should instead be a bind variable, perhaps :iw, or some other mechanism of inputting the ISO week number into the query.
select trunc(sysdate, 'iw') - 7 * (to_number(to_char(trunc(sysdate), 'iw')) - 14) as dt
from dual;
DT
----------
2016-04-04
1 row selected.

sql database function (cut-off time on 4.30pm)

I'm try to write SQL Database function. It should count number of working day with a cut-off point at 4.30pm. So any orders before 4.30pm appear in the total for the day before, and after 4.30pm in the total for that day. I found the code that count number of working day but I know how to add the cut-off point into the code?
create or replace
function "number_of_worked_day"
(p_start_dt date,
p_end_dt date
)
return number as
L_Number_Of_Days Number;
L_Start_Dt Date;
L_end_dt DATE;
Begin
L_Start_Dt :=Trunc(P_Start_Dt);
L_end_dt := trunc(p_end_dt);
SELECT COUNT(*)
into l_number_of_days
FROM (
WITH date_tab AS (SELECT TO_DATE (L_Start_Dt) + LEVEL - 1 business_date
FROM DUAL
CONNECT BY LEVEL <=
TO_DATE (L_end_dt)
- TO_DATE (L_Start_Dt)
+ 1)
SELECT business_date
FROM date_tab
WHERE TO_CHAR (business_date, 'DY') NOT IN ('SAT', 'SUN')
AND NOT EXISTS
( SELECT 1
FROM PUBLIC_HOLIDAY
WHERE business_date between START_DT and END_DT));
return l_number_of_days;
end;
I find your code a bit hard to follow. But the logic for handling a day starting at 4:30 p.m. is to subtract 16.5 hours from the time. For instance:
SELECT trunc(business_date - 16.5/24), count(*)
FROM date_tab
GROUP BY trunc(business_date - 16.5/24)
ORDER BY trunc(business_date - 16.5/24);

Calling a function that return a table with dates

I have a problem with a function.
I need to get a table with dates of a month, name of the day and the amound of weekdays in that month.
I already found some help here and adapted it to my need, but I can't get the function running/compiled
Here is what I have so far:
create or replace TYPE DATE_ROW AS OBJECT
(
MyDate DATE,
Dayname VARCHAR2(12),
Amount Integer
)
create or replace TYPE DATE_TABLE as table of DATE_ROW
create or replace FUNCTION myfinaldays (mydate date)
RETURN DATE_TABLE
PIPELINED
IS
V_MYDATE DATE;
V_DAYNAME VARCHAR2(12);
V_AMOUNT NUMBER;
BEGIN
with this_month as (
select trunc(to_date('mydate'), 'mm')+level-1 dy
from dual
connect by level < (trunc(add_months(to_date('mydate'),1), 'mm')- trunc(to_date('mydate'), 'mm'))+1
)
FOR i IN 0 .. (select count(*) from this_month) LOOP
select (dy) Daydate,
to_char(dy, 'day'), Dayname,
( select count(*)
from this_month
where to_char(dy, 'dy') not in ('sat', 'sun')
) Amount
from this_month
where to_char(dy, 'dy') not in ('sat', 'sun')
and EXTRACT(day from to_date(dy)) = i;
pipe row (date_row(v_mydate,v_dayname, v_amount));
END LOOP;
RETURN;
END;
The function call would then be something like:
select * from date_table(cast(myfinaldays('01.02.12')));
I can only give one date as a parameter.
I hope someone can help me out here since this is slowly driving me mad.
Any ideas, examples, or thoughts would be greatly appreciated.
UPDATE:
Ok here is an update with mybe some more helpfull infos to my problem:
This is working and my aim is to put this in a function so I can call it with 1 parameter:
with this_month as (
select trunc(to_date('01.02.12'), 'mm')+level-1 dy
from dual
connect by level < (trunc(add_months(to_date('01.02.12'),1), 'mm')- trunc(to_date('01.02.12'), 'mm'))+1
)
select (dy) mydate, (select count(*) from this_month) Days_in_month
, to_char(dy, 'day') Dayname
, ( select count(*) from this_month where to_char(dy, 'dy') not in ('sat', 'sun') ) Amount
from this_month
where to_char(dy, 'dy') not in ('sat', 'sun') ;
for the loop I added: 'and EXTRACT(day from to_date(dy))=i' at the end.
I added a date so you can see what I need in the end. If I write mydate instead and enter 01.02.12 as parameter after running it in the developer it still works.
Errors I get compiling:
- Error(10,1): PL/SQL: SQL Statement ignored
- Error(15,5): PL/SQL: ORA-00928: Keyword SELECT missing
- Error(22,8): PLS-00113: END-Definer 'LOOP' must complete 'myfinaldays' in row 1, comlumn 10
- Error(23,4): PLS-00103: Found the symbol "RETURN"
The errors are translated since my Oracle does not run english so I hope to have guessed it correctly.
You have a number of problems here.
You can't use a select directly in for i in 1..x (and it should be 1, not 0), you'd have to select into a local variable and use that as the end value.
You can't use with as an input to a for loop; but you can use a cursor there, as I have below.
The select inside the loop with get no-data-found when i represents a Saturday or Sunday (the 4th in this case, assuming you're running it for February).
Getting the count of all non-weekend rows for every row is inefficient, though it doesn't really matter for such a small amount of data.
You are referring to to_date('mydate'); mydate is already a date, and 'mydate' is a string that has no relation to it.
And maybe some other things too, like assuming the date format and locale, since to_char(x, 'dy') will not return sat or sun everywhere, but I'll assume this is going to be used in a restricted way so you don't care about that too much.
From trying to figure out what output you're expecting, I think this will work (with modified types to get the days_in_month you added to the original question):
create or replace type date_row as object
(
mydate DATE,
dayname VARCHAR2(12),
days_in_month NUMBER,
amount NUMBER
)
/
create or replace type date_table as table of date_row
/
create or replace function myfinaldays (mydate date)
return date_table pipelined deterministic is
begin
for r in (
select *
from (
select dy as daydate,
to_char(dy, 'day') as dayname,
count(*) over () as days_in_month,
sum(case when to_char(dy, 'dy') in ('sat', 'sun')
then 0 else 1 end) over () as amount
from (
select trunc(mydate, 'mm')+level-1 dy
from dual
connect by level < (trunc(add_months(mydate, 1), 'mm')
- trunc(mydate, 'mm')) + 1
)
)
where to_char(daydate, 'dy') not in ('sat', 'sun')
) loop
pipe row (date_row(r.daydate, r.dayname, r.days_in_month, r.amount));
end loop;
end myfinaldays;
/
(If you don't want days_in_month then you can move the sat/sun check up and use the count for amount instead of the sum(case). The outer select is currently filtering on the day of the week so you can count all days, but that's not an issue if you don't want that value).
Then call as:
alter session set nls_date_format = 'DD.MM.RR';
select * from table(myfinaldays(to_date('01.02.12', 'DD.MM.RR')));
MYDATE DAYNAME DAYS_IN_MONTH AMOUNT
-------- ------------ ------------- ----------
01.02.12 wednesday 29 21
02.02.12 thursday 29 21
03.02.12 friday 29 21
06.02.12 monday 29 21
...
29.02.12 wednesday 29 21
21 rows selected.
Since your function is a table valued function (returns a table), you cannot call it as regular function.
you have join with it:
something like this in Oracle:
select * from date_table t
cross join table( myfinaldays(<t.date colum>))

How to populate calendar table in Oracle?

I want to maintain a calender table in Oracle DB which I want to populate with all the days of the year starting from 2011 to 2013 (it may be till any year). How can I do that?
Consider my DB table has columns and example dataset is:
S.No Cal_Dt DayName
1 01-01-2011 Monday
2 02-01-2011 Tuesday
3 03-01-2011 Wednesday
and so on.
I am more concerned with the Cal_Dt only here (DayName is optional).
This is a simple and easy way to do it
with calendar as (
select :startdate + rownum - 1 as day
from dual
connect by rownum < :enddate - :startdate
)
select rownum as "S.No", to_date(day,'dd_mm_yyyy') as "Cal_Dt", to_char(day,'day') as "DayName"
from calendar
with calendar as (
select rownum - 1 as daynum
from dual
connect by rownum < sysdate - to_date('1-jan-2010') + 1
)
select to_date('1-jan-2010') + daynum as monthdate
from calendar
;
declare
v_date date := to_date('20110101','yyyymmdd');
begin
while v_date < sysdate + 720 loop
insert into calender
values ( v_date, to_char(v_date,'DAY'));
v_date := v_date + 1;
end loop;
commit;
end;
/
This is not best practice and you should use Allesandro Rossi's solution. This may only be useful if you're using Oracle 9i or earlier and populating a large table.

How do I select dates between two given dates in an Oracle query?

How do I select dates between two given dates in an Oracle query?
SELECT TO_DATE('12/01/2003', 'MM/DD/YYYY') - 1 + rownum AS d
FROM all_objects
WHERE TO_DATE('12/01/2003', 'MM/DD/YYYY') - 1 + rownum <= TO_DATE('12/05/2003', 'MM/DD/YYYY')
from
http://forums.devshed.com/oracle-development-96/select-all-dates-between-two-dates-92997.html
SELECT * FROM your_table WHERE your_date_field BETWEEN DATE '2010-01-01' AND DATE '2011-01-01';
You can use the LEVEL pseudocolumn in a tricky way to generate a series, so, for example, to get the list of days between today and 20 days from now I can:
select trunc(sysdate+lvl) from
(select level lvl from dual connect by level < ((sysdate+20)-sysdate - 1) )
order by 1
Generically you can see how this would apply for any two given dates.
select trunc(early_date+lvl) from
(select level lvl from dual connect by level < (later_Date-early_date-1) )
order by 1
And you can adjust the clauses if you want to include the two end dates as well.
You could also use the below to get a list of calendar dates between a date range (similar to Michael Broughton's solution)
select (trunc(sysdate) - (trunc(sysdate) - (to_date('start_date')))) -1 + level from dual
connect by level <=
((select (trunc(sysdate) - (trunc(sysdate) - (to_date('end_date'))))-
(trunc(sysdate) - (trunc(sysdate) - (to_date('start_date'))))from dual)+1);
I do this so often for a scheduling app I work on that I created a pipelined table function. Sometimes I need days, hours or 15 minutes between times. This is not exactly my function, because my code is in a package. For example, here, I'm getting days between Jan 1 2020 and Jan 10 2020:
SELECT
days.date_time
FROM
table(between_times(TO_DATE('2020-01-01'),TO_DATE('2020-01-10'),(60*24), 'Y')) days
The pipelined function:
function between_times(i_start_time TIMESTAMP, i_end_time TIMESTAMP, i_interval_in_minutes NUMBER, include_end_time VARCHAR2 := 'N')
RETURN DateTableType PIPELINED
AS
time_counter TIMESTAMP := i_start_time;
BEGIN
IF i_start_time IS NULL OR i_end_time IS NULL or i_start_time > i_end_time OR i_interval_in_minutes IS NULL OR
i_interval_in_minutes <= 0 THEN
RETURN;
END IF;
LOOP
-- by default does not include end time
if (include_end_time = 'Y') THEN
exit when time_counter > i_end_time;
ELSE
exit when time_counter >= i_end_time;
END IF;
pipe row(DateType( time_counter ));
time_counter := time_counter + i_interval_in_minutes/(60*24);
END LOOP;
EXCEPTION WHEN NO_DATA_NEEDED THEN NULL;
END;
Use "between". In a general sense:
select * from someTable where dateCol between date1 and date2;
note that dateCol is defined as a date and date1 and date2 are also date values. If these aren't dates, then you'll convert them to dates using to_date function.
with all_days as (select trunc(to_date('12-03-2017','dd-mm-yyyy')+levl)-1 as all_dates from
(select level levl from dual connect by level < (sysdate-to_date('12-03-2017','DD-MM-YYYY')+1) )
order by 1)
select count(*) as no_of_days from all_days where ltrim(rtrim(to_char(all_dates,'DAY'))) not in ('SATURDAY','SUNDAY');