How to populate calendar table in Oracle? - sql

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.

Related

How to calculate number of leap years between two dates in Oracle?

I want to calculate the number of leap years between the hire date of the employee and the current date (on hr.employees table in Oracle SQL Developer). How to do this?
A leap year consists of 366 days. I assume a "leap year" between two dates consists of all the days from Jan 1 to Dec 31 of a year with Feb 29th.
Based on this understanding, there is a pretty simple solution.
Count the number of days between the Jan 1 of the year following the hire date and Jan 1 of the year of the end date.
Count the number of years between those two dates.
Subtract the difference between the days and the number of years * 365
Happily, built-in functions do most of the work.
This results in:
(trunc(sysdate, 'YYYY') -
trunc(hiredate + interval '1' year, 'YYYY') -
365 * (extract(year from sysdate) - extract(year from hiredate) - 1)
) as num_years
It is a little trickier to count leap days but that is not what the question is asking.
You can reuse the code Oracle has already written: just check if creating a leap day raises an exception:
SELECT TO_DATE('2016-02-29','YYYY-MM-DD') FROM DUAL;
29.02.2016
but
SELECT TO_DATE('2018-02-29','YYYY-MM-DD') FROM DUAL;
ORA-01839: date not valid for month specified
So you just have to count the exceptions:
CREATE OR REPLACE FUNCTION count_leap_years (p_from DATE, p_to DATE) RETURN NUMBER
IS
number_of_leap_days NUMBER := 0;
date_not_valid EXCEPTION;
PRAGMA EXCEPTION_INIT(date_not_valid, -1839);
BEGIN
FOR y IN EXTRACT(YEAR FROM p_from) .. EXTRACT(YEAR FROM p_to) LOOP
DECLARE
d DATE;
BEGIN
d := TO_DATE(to_char(y,'fm0000')||'-02-29', 'YYYY-MM-DD');
IF p_from < d AND d < p_to THEN
number_of_leap_days := number_of_leap_days + 1;
END IF;
EXCEPTION WHEN date_not_valid THEN
NULL;
END;
END LOOP;
RETURN number_of_leap_days;
END count_leap_years;
/
Something like this will allow you to select numbers, in this case, from 1 to 2999.
Select Rownum year
From dual
Connect By Rownum <= 2999
Something like this will allow you to check if a specific year is a leap year or not
CASE WHEN ((MOD(YEAR, 4) = 0 AND (MOD(YEAR, 100) <> 0)) OR MOD(year, 400) = 0) THEN 1 ELSE 0 END as isLeapYear
Now you just need to add the filtering and the sum of the leap years.
Select sum(isLeapYear)
from (
Select year, CASE WHEN ((MOD(YEAR, 4) = 0 AND (MOD(YEAR, 100) <> 0)) OR MOD(year, 400) = 0) THEN 1 ELSE 0 END as isLeapYear
FROM (
Select Rownum year
From dual
Connect By Rownum <= 2999
) a
Where a.year >= EXTRACT(YEAR FROM DATE %DateStart%) and a.year <= EXTRACT(YEAR FROM DATE %DateEnd%)
) b
This can be really improved in terms of performance, but like this I think its easier do understand what each step is doing and then you can decompose it into what you really want and expect.
try this:
CREATE OR REPLACE FUNCTION LEAP_YEARS(EMP_ID IN NUMBER)
RETURN NUMBER
IS
HIRE_YEAR NUMBER:=0;
SYS_YEAR NUMBER:=0;
NUMBER_LEAP_YEARS NUMBER:=0;
BEGIN
SELECT EXTRACT(YEAR FROM HIRE_DATE) INTO HIRE_YEAR
FROM EMPLOYEE
WHERE EMPLOYEE_ID = EMP_ID;
SELECT EXTRACT(YEAR FROM SYSDATE()) INTO SYS_YEAR
FROM DUAL;
FOR IDX IN HIRE_YEAR..SYS_YEAR LOOP
IF MOD(IDX,4)=0 THEN
NUMBER_LEAP_YEARS := NUMBER_LEAP_YEARS + 1;
END IF;
END LOOP;
RETURN NUMBER_LEAP_YEARS;
END;
I tested it on a toy example and it works. Maybe you need to improve it (hint: maybe it needs some exceptions and it assume that hire_date < sysdate).
Then you can use it as:
SELECT LEAP_YEARS(E.EMPLOYEE_ID) FROM EMPLOYEE E;

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);

How to get last thursday of every month in a year 2013 in oracle?

How to get last Thursday of every month in a year 2013 in oracle? i need to update this date into my table.
i need a output like
Last Thursday in a year 2013
----------------------
31.01.2013
28.02.2013
28.03.2013
24.04.2013
30.05.2013
27.06.2013
25.07.2013
29.08.2013
26.09.2013
31.10.2013
28.11.2013
26.12.2013
Thanks to do the needful.
This will do it:
select next_day (last_day (add_months(date '2013-01-01', rownum-1))-7, 'THU') as thurs
from dual
connect by level <= 12;
THURS
---------
31-JAN-13
28-FEB-13
28-MAR-13
25-APR-13
30-MAY-13
27-JUN-13
25-JUL-13
29-AUG-13
26-SEP-13
31-OCT-13
28-NOV-13
26-DEC-13
12 rows selected.
Explanation:
1) The following select is a way to generate a series of integers 1..12:
select rownum from dual connect by level <= 12;
2) This returns the 1st of each of the 12 months of 2012 by taking 1st January 2013 and adding 0 months, 1 month, ..., 11 months:
select add_months(date '2013-01-01', rownum-1)
from dual connect by level <= 12;
3) The last_day function returns the last day of the month for the given date, so that we now have 2013-01-31, 2013-02-28, ..., 2013-12-31.
4) next_day (date, 'THU') returns the next Thursday after the specified date. To get the last Thursday of the month we take the last day of the month, go back 7 days, then find the next Thursday.
I'd go with dbms_scheduler:
declare
start_dt date := date '2013-01-01';
months_last_thursday date;
begin
loop
dbms_scheduler.evaluate_calendar_string (
calendar_string => 'FREQ=MONTHLY;BYDAY=-1 THU',
start_date => start_dt,
return_date_after => start_dt,
next_run_date => months_last_thursday
);
exit when months_last_thursday > date '2013-12-31';
dbms_output.put_line(months_last_thursday);
start_dt := months_last_thursday;
end loop;
end;
/
Building on #Rene's answer, this could be extended into a general (generic?) solution:
CREATE OR REPLACE FUNCTION recurring_dates (p_year IN NUMBER, p_rule IN VARCHAR)
RETURN dbms_stats.datearray PIPELINED
AS
start_dt DATE;
last_dt DATE;
next_dt DATE;
BEGIN
start_dt := to_date(to_char(p_year,'fm0000')||'01-01','YYYY-MM-DD');
last_dt := to_date(to_char(p_year,'fm0000')||'12-31','YYYY-MM-DD');
LOOP
dbms_scheduler.evaluate_calendar_string(
calendar_string => p_rule,
start_date => start_dt,
return_date_after => start_dt,
next_run_date => next_dt);
EXIT WHEN next_dt > last_dt;
PIPE ROW (next_dt);
start_dt := next_dt;
END LOOP;
END recurring_dates;
/
You'd feed the function with a calendar string in the DBMS_SCHEDULER-Syntax, and it will return the matching dates.
#rcmuthu786's last thursdays:
SELECT * FROM TABLE(recurring_dates (2013, 'FREQ=MONTHLY;BYDAY=-1 THU'));
2013-01-31
2013-02-28
2013-03-28
2013-04-25
2013-05-30
...
Or, the second Wednesdays of each month:
SELECT * FROM TABLE(recurring_dates (2013, 'FREQ=MONTHLY;BYDAY=2 WED'));
2013-01-09
2013-02-13
2013-03-13
2013-04-10
...
etc, etc...
The most elegant way is the following
select thdate from (
select a.*, row_number() over (partition by to_char(thdate,'yyyymm'),day order by thdate desc) row_rank
from (
select trunc(sysdate,'YEAR') + rownum-1 thdate,
trim(to_char(trunc(sysdate,'YEAR') + rownum-1,'DAY')) day from dba_objects
where trunc(sysdate,'YEAR') + rownum-1 <
trunc(sysdate+365,'YEAR')
) a where day='THURSDAY'
) where row_rank=1

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');

Number of fridays between two dates

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