Oracle date difference to get number of years - oracle

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

Related

how to get year and month into number oracle

i'm trying to get sysdate year and month into number or char.
I'd want something like this:
202107
or last month
202106
i tried this code:
select trunc(add_months(sysdate, -1), 'MM') from dual;
please help, thanks
Convert it to a string and then to a number:
SELECT TO_NUMBER(TO_CHAR(ADD_MONTHS(SYSDATE,-1), 'YYYYMM'))
FROM DUAL;
Or, you can use extract and wrap it in a sub-query so you do not need to repeat adding the months:
SELECT EXTRACT(YEAR FROM dt) * 100 + EXTRACT(MONTH FROM dt)
FROM (
SELECT ADD_MONTHS(SYSDATE, -1) AS dt
FROM DUAL
)
Like
SELECT EXTRACT(year from sysdate) * 100 + EXTRACT(month from sysdate)
If you want to scroll around, manipulate the date before you extract from it, rather than minusing after you extract (gets tricky to e.g. go back a month if the date is in jan)
--will work for jan 2021
SELECT EXTRACT(year from ADD_MONTHS(somedate, -1)) * 100 + EXTRACT(month from ADD_MONTHS(somedate, -1))
--won't work for jan 2021
SELECT EXTRACT(year from somedate) * 100 + (EXTRACT(month from somedate) - 1)
TRUNC is a device that "rounds" a date to a particular interval, such as "trimming the time off a datetime" or "making any day of the week back to the date that was the start of the week" - very useful for all sorts of stuff like "total sales by month - SUM(sale) GROUP BY TRUNC(saledate, 'mm')" but it keeps all the components of the date (the day, the hour, the minute etc) so it isn't what you want.

weekly calender table in oracle to update alternative week flag & date

I'm creating a table to populate a weekly calendar table and my requirement is to update wk_start_date as every week sunday and populate the pr_wk_flag as Y every two weeks and
beg dt & end dt would be from two weeks before Thursday to current Wednesday
Like below, How to populate in oracle plsql
Wk_start_date pr_wk_flag beg dt end dt
2/9/2014 Y 1/30/2014 2/12/2014
2/16/2014 N NULL NULL
2/23/2014 Y 2/13/2014 2/26/2014
3/2/2014 N NULL NULL
3/9/2014 Y 2/27/2014 3/12/2014
Here's one way:
select wk_start_dt, pr_wk_flag,
case when pr_wk_flag = 'Y' then wk_start_dt - 10 end as beg_dt,
case when pr_wk_flag = 'Y' then wk_start_dt + 3 end as end_dt
from (
select next_day(date '2014-02-01' + (7 * level), 'SUN') as wk_start_dt,
decode(mod(level, 2), 1, 'Y', 'N') as pr_wk_flag
from dual
connect by level <= 6
);
The inner query is a common way to generate a sequence of values. Adjust the start date and the number of iterations to change the range of week-starts generated, and if necessary to flip the flag value. The SUN argument to the next_day function has to be in your local session language, so change that to something suitable if you're not using English. As a one-off insert I don't think the NLS-sensitivity is an issue in this case.
The outer query just uses the flag to decide whether to calculate the other two dates, or leave them null.
SQL Fiddle demo.
There you have for all 2014 year
SELECT dates+(8- to_char(TRUNC (TRUNC (dates, 'w'), 'YEAR'),'d')) wk_start_date,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2), 0, 'Y', 'N')
pr_wk_flag,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2),
0, dates + 5 - 17,
NULL)
pr_wk_flag,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2), 0, dates + 3, NULL)
end_dt
FROM (SELECT TRUNC (SYSDATE, 'YEAR') + 7 * (ROWNUM - 1) dates
FROM dba_tables
WHERE TRUNC (TRUNC (SYSDATE, 'YEAR') + 7 * (ROWNUM - 1), 'YEAR') =
TRUNC (SYSDATE, 'YEAR'))
ORDER BY wk_start_date ASC

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;

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

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;

How to get a list of months between 2 given dates using a query?

I have 2 dates, say 28-Mar-2011 and 29-Jun-2011. I need an sql query that will display the months between these 2 dates including the months containing the dates, ie. June, May, April and March.
Something like this
SQL> ed
Wrote file afiedt.buf
select to_char( add_months( start_date, level-1 ), 'fmMonth' )
from (select date '2011-03-30' start_date,
date '2011-06-29' end_date
from dual)
connect by level <= months_between(
trunc(end_date,'MM'),
trunc(start_date,'MM') )
* + 1
SQL> /
TO_CHAR(ADD_MONTHS(START_DATE,LEVEL-
------------------------------------
March
April
May
June
should work.
Gonna add this solution just because I think it's much cleaner than the others:
SELECT ADD_MONTHS(TRUNC(TO_DATE('28-Mar-2011', 'DD-MON-YYYY'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE('28-Mar-2011', 'DD-MON-YYYY'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE('29-Jun-2011', 'DD-MON-YYYY'), 'MON')
You can use the function MONTHS_BETWEEN
SELECT MOD( TRUNC( MONTHS_BETWEEN( '2011-07-29', '2011-03-28' ) ), 12 ) as MONTHS
FROM DUAL
Output
MONTHS
----------
4
I needed an answer to this a couple of days ago. I found another solution I liked more:
select to_char(which_month, 'Mon-yyyy') month
from
(
select
add_months(to_date(:start_date,'mm-yyyy'), rownum-1) which_month
from
all_objects
where
rownum <= months_between(to_date(:end_date,'mm-yyyy'), add_months(to_date(:start_date,'mm-yyyy'), -1))
order by
which_month
)
You could of course use any format you want. I 'union'ed and summed over another set so that I'd get the months even when they didn't have results.
SELECT MIN (to_date((TO_CHAR (Actual_Date, 'DD-MM-RRRR')),'dd-mm-rrrr')) F_DATE,
MAX (to_date((TO_CHAR (Actual_Date, 'DD-MM-RRRR')),'dd-mm-rrrr')) T_DATE,
TO_CHAR (Actual_Date, 'MM-RRRR') TRX_MONTH
FROM ( SELECT TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr')) + LEVEL - 1
Actual_Date
FROM (SELECT TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr'), 'MM') - 1
AS dt
FROM DUAL)
CONNECT BY LEVEL <=
( TO_DATE (:P_TDATE, 'dd-mm-rrrr')
- TRUNC (TO_DATE (:P_FDATE, 'dd-mm-rrrr'))
+ 1))
GROUP BY TO_CHAR (Actual_Date, 'MM-RRRR')
ORDER BY 1
declare
v_date_from_first_day date;
v_date_to_last_day date;
v_month_name varchar2(10);
v_month_number number;
v_year_number number;
v_month_diff number;
begin
v_date_to_last_day := to_date('31.12.2018');
v_date_from_first_day := to_date('01.01.2018');
select months_between(v_date_to_last_day,v_date_from_first_day) as diff into v_month_diff from dual;
for i in 1..round(v_month_diff, 2) loop
select
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'fmMonth') as month_nm,
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'MM') as month_num,
to_char(trunc(add_months(v_date_to_last_day - months_between(v_date_from_first_day, v_date_to_last_day), -i)), 'YYYY') as year_num
into v_month_name, v_month_number, v_year_number
from dual;
dbms_output.put_line(v_month_number || '/' || v_year_number);
dbms_output.put_line(v_month_name || '/' || v_year_number);
end loop;
end;
Output:
12/2018
11/2018
10/2018
9/2018
8/2018
7/2018
6/2018
5/2018
4/2018
3/2018
2/2018
1/2018
Here, month names are in Croatian
Prosinac/2018
Studeni/2018
Listopad/2018
Rujan/2018
Kolovoz/2018
Srpanj/2018
Lipanj/2018
Svibanj/2018
Travanj/2018
Ožujak/2018
Veljača/2018
Siječanj/2018

Resources