Get same day of the same week last year - sql

I'm looking for a way to get the same last year's day for a specific date.
I have a table with date's and want in a second column the same day but last year's same day.
Example:
Date: Monday 01 June 2009
Last year Date: Monday 02 June 2008
So it has to be the same day of the same week but 1 year earlier.
Is there a way of doing this?
Thanks for the help?

Try:
select add_months(sysdate, -12) + (to_char(sysdate, 'D') - to_char(add_months(sysdate, -12), 'D'))
from dual
Output:
6/10/2014 12:07:01 PM
Which is also a Tuesday like today.

You can use this one:
SELECT
NEXT_DAY(ADD_MONTHS(DATE '2009-06-01', -12)-1, TO_CHAR(DATE '2009-06-01', 'fmDAY')) AS Last_year_day
FROM dual;
Result:
2008-06-02
Of course, it always returns the next day, i.e. in worst case 6 days later.

Assume by "week" you mean the week as defined by ISO-8601, you can use a function which converts the week number to a date.
CREATE FUNCTION IsoWeekDate(iso_year IN INTEGER, iso_week IN INTEGER) RETURN DATE AS
BEGIN
RETURN TRUNC(TO_DATE(iso_year || '0104', 'YYYYMMDD'), 'IW') + 7 * (iso_week - 1);
END IsoWeekDate;
Then you can do it like this:
SELECT
NEXT_DAY(IsoWeekDate(TO_CHAR(DATE '2009-06-01', 'IYYY')-1, TO_CHAR(DATE '2009-06-01', 'IW'))-1, TO_CHAR(DATE '2009-06-01', 'fmDAY'))
FROM YOUR_TABLE
Of course, you can write all in one in SQL, I split it only for better visibility.
Note, for dates between 29th of December and 3rd of January you may get unexpected results when you use this approach, because a year can have 52 or 53 weeks and these dates can belong to previous, resp. next year.

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE FUNCTION TO_ISO_WEEK_DATE(
week NUMBER,
year NUMBER
) RETURN DATE DETERMINISTIC
IS
BEGIN
RETURN NEXT_DAY(
TO_DATE( TO_CHAR( year, '0000' ) || '0104', 'YYYYMMDD' )
- INTERVAL '7' DAY, 'MONDAY'
)
+ ( week - 1 ) * 7;
END TO_ISO_WEEK_DATE;
/
CREATE FUNCTION SAME_DAY_AND_WEEK_OF_YEAR(
dt DATE,
year NUMBER
) RETURN DATE DETERMINISTIC
IS
p_week NUMBER(2,0) := TO_NUMBER( TO_CHAR( dt, 'IW' ) );
p_day VARCHAR2(9) := TO_CHAR( dt, 'DAY' );
BEGIN
RETURN NEXT_DAY( TO_ISO_WEEK_DATE( p_week, year ) - 1, p_day );
END SAME_DAY_AND_WEEK_OF_YEAR;
/
Query 1:
SELECT TO_CHAR( SYSDATE + LEVEL - 1, 'YYYY-MM-DD (IW-D)' ) AS "Date 2015",
TO_CHAR( SAME_DAY_AND_WEEK_OF_YEAR( SYSDATE + LEVEL - 1, 2014 ), 'YYYY-MM-DD (IW-D)' ) AS "Date 2014",
TO_CHAR( SAME_DAY_AND_WEEK_OF_YEAR( SYSDATE + LEVEL - 1, 2013 ), 'YYYY-MM-DD (IW-D)' ) AS "Date 2013",
TO_CHAR( SAME_DAY_AND_WEEK_OF_YEAR( SYSDATE + LEVEL - 1, 2012 ), 'YYYY-MM-DD (IW-D)' ) AS "Date 2012",
TO_CHAR( SAME_DAY_AND_WEEK_OF_YEAR( SYSDATE + LEVEL - 1, 2011 ), 'YYYY-MM-DD (IW-D)' ) AS "Date 2011"
FROM DUAL
CONNECT BY LEVEL <= 7
Results:
| Date 2015 | Date 2014 | Date 2013 | Date 2012 | Date 2011 |
|-------------------|-------------------|-------------------|-------------------|-------------------|
| 2015-06-12 (24-6) | 2014-06-13 (24-6) | 2013-06-14 (24-6) | 2012-06-15 (24-6) | 2011-06-17 (24-6) |
| 2015-06-13 (24-7) | 2014-06-14 (24-7) | 2013-06-15 (24-7) | 2012-06-16 (24-7) | 2011-06-18 (24-7) |
| 2015-06-14 (24-1) | 2014-06-15 (24-1) | 2013-06-16 (24-1) | 2012-06-17 (24-1) | 2011-06-19 (24-1) |
| 2015-06-15 (25-2) | 2014-06-16 (25-2) | 2013-06-17 (25-2) | 2012-06-18 (25-2) | 2011-06-20 (25-2) |
| 2015-06-16 (25-3) | 2014-06-17 (25-3) | 2013-06-18 (25-3) | 2012-06-19 (25-3) | 2011-06-21 (25-3) |
| 2015-06-17 (25-4) | 2014-06-18 (25-4) | 2013-06-19 (25-4) | 2012-06-20 (25-4) | 2011-06-22 (25-4) |
| 2015-06-18 (25-5) | 2014-06-19 (25-5) | 2013-06-20 (25-5) | 2012-06-21 (25-5) | 2011-06-23 (25-5) |

Just add 364 whether it is a leap year or not eg
select
to_char(to_date('24/01/2016','dd/mm/yyyy'),'DAY DD/MM/YYYY') as CrossesLeapYear_PivotDate,
to_char(add_months(to_date('24/01/2016','dd/mm/yyyy'),12), 'DAY DD/MM/YYYY') as CrossesLeapYear_NextYearDate,
to_char(to_date('24/01/2016','dd/mm/yyyy') + 364, 'DAY DD/MM/YYYY') as CrossesLeapYear_SameNextYear,
to_char(to_date('24/01/2017','dd/mm/yyyy'),'DAY DD/MM/YYYY') as NoLeapYear_PivotDate,
to_char(add_months(to_date('24/01/2017','dd/mm/yyyy'),12), 'DAY DD/MM/YYYY') as NoLeapYear_NextYearDate,
to_char(to_date('24/01/2017','dd/mm/yyyy') + 364, 'DAY DD/MM/YYYY') as NoLeapYear_SameNextYear
from dual

Related

How to sort by ISO week and begin with today's week number

I want to query data from oracle and sort it by week, but the result is begin with week 52 and now is week 44 actually.
this is my sql :
SELECT *
FROM (SELECT
to_char(contract.MIGRATION_SUCCESS_DATE,'yyyy-iw') metric,
sum(contract.BLIS_MRR) mrr,
count(contract.CONTRACT_ID) count
FROM (SELECT DISTINCT
CONTRACT_ID,
BLIS_MRR,
MIGRATION_SUCCESS_DATE
FROM MR_MIGRATION_SITE) contract WHERE MIGRATION_SUCCESS_DATE < sysdate
GROUP BY to_char(contract.MIGRATION_SUCCESS_DATE,'yyyy-iw'))
ORDER BY metric DESC;
and the following picture is result:
I think you have to use format to_char(contract.MIGRATION_SUCCESS_DATE,'iyyy-iw')
The year of ISO week can be different to actual year, for example January 1st 2017 was week 52 of 2016, i.e. 2016-W52 according ISO definition!
I recommend format 'iyyy-"W"iw' which is compliant to ISO 8601
And perhaps change your GROUP BY clause to GROUP BY TRUNC(contract.MIGRATION_SUCCESS_DATE,'iw')
You get the problem when the date and the Monday of that date's week are in different years.
To fix it you can use #Wernfried Domscheit's solution and the iyyy-iw format to get the ISO year and week:
TO_CHAR( contract.MIGRATION_SUCCESS_DATE, 'IYYY-IW' )
Initial (Incorrect) Solution:
To fix it you can truncate the date to the start of the ISO week and then convert it to yyyy-iw format:
TO_CHAR(
TRUNC( contract.MIGRATION_SUCCESS_DATE, 'IW' )
'yyyy-iw'
)
For example:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test_data( dt ) AS
SELECT DATE '2016-12-31' FROM DUAL UNION ALL
SELECT DATE '2017-01-01' FROM DUAL UNION ALL
SELECT DATE '2017-01-02' FROM DUAL UNION ALL
SELECT DATE '2014-12-31' FROM DUAL;
Query 1:
SELECT dt,
TO_CHAR( dt, 'iyyy-iw' ) AS trunc_iw,
TO_CHAR( TRUNC( dt, 'IW' ), 'yyyy-iw' ) AS trunc_iw2,
TO_CHAR( dt, 'yyyy-iw' ) AS non_trunc_iw
FROM test_data
Results:
| DT | TRUNC_IW | TRUNC_IW2 | NON_TRUNC_IW |
|----------------------|----------|-----------|--------------|
| 2016-12-31T00:00:00Z | 2016-52 | 2016-52 | 2016-52 |
| 2017-01-01T00:00:00Z | 2016-52 | 2016-52 | 2017-52 |
| 2017-01-02T00:00:00Z | 2017-01 | 2017-01 | 2017-01 |
| 2014-12-31T00:00:00Z | 2015-01 | 2014-01 | 2014-01 | -- initial version is incorrect for this date

How to get Month, Year and Days in postgresql between two dates

I have two columns in the sql table which is startdate and enddate
Startdate Enddate
27-12-2015 22:30 03-01-2016 19:30
01-01-2016 12:45 09-02-2016 18:30
I want to get the resultant table like
Startdate Enddate Month year days
27-12-2015 22:30 03-01-2016 19:30 Dec 2015 5
27-12-2015 22:30 03-01-2016 19:30 Jan 2016 3
01-01-2016 12:45 09-02-2016 18:30 Jan 2016 31
01-01-2016 12:45 09-02-2016 18:30 Feb 2016 9
A rough solution would be to generate all the days and then aggregate (count) them. It works, but it's rough on memory. If it's not crucial, this solution would definitely work. The alternative is to generate a months series and make a day diff with a lot of conditions, if performance is critical.
SELECT
dates.startdate::DATE,
dates.enddate::DATE,
to_char(days.s, 'Mon') AS mon,
to_char(days.s, 'YYYY') AS yr,
count(1) AS d
FROM dates
CROSS JOIN LATERAL (
SELECT * FROM generate_series(dates.startdate, dates.enddate, INTERVAL '1 day') s
) days
GROUP BY 1, 2, 3, 4
In any case, here is the second variant, that loops through months instead (faster, harder to understand):
SELECT
dates.startdate::DATE,
dates.enddate::DATE,
to_char(months.startdate, 'Mon') AS mon,
to_char(months.startdate, 'YYYY') AS yr,
least(
months.enddate::DATE - dates.startdate::DATE + 1, -- takes care of first month
dates.enddate::DATE - months.startdate::DATE + 1, -- takes care of last month
months.enddate::DATE - months.startdate::DATE + 1 -- takes care of full months from the middle of the intervals
) AS "days"
FROM dates
-- get months as first day in that month
CROSS JOIN LATERAL (
SELECT * FROM generate_series(
(to_char(dates.startdate, 'YYYY-MM-') || '01')::DATE,
(to_char(dates.enddate + INTERVAL '1 month', 'YYYY-MM-') || '01')::DATE - 1, INTERVAL '1 month') m
) days
-- get months as start date and end date
CROSS JOIN LATERAL (
SELECT
days.m::DATE AS startdate,
(days.m + INTERVAL '1 month')::DATE - 1 AS enddate
) months
In this particular case a plpgsql function can provide better performance than a plain sql query.
create or replace function get_months(startdate date, enddate date)
returns table (mon text, year int, days int)
language plpgsql as $$
declare d date;
begin
d:= date_trunc('month', startdate);
while d < enddate loop
mon:= to_char(d, 'Mon');
year:= to_char(d, 'YYYY');
days:= case
when d+ '1month'::interval > enddate then enddate- d+ 1
when d < startdate then (d+ '1month'::interval)::date- startdate
else (d+ '1month'::interval)::date- d
end;
return next;
d:= d+ '1month'::interval;
end loop;
end
$$;
Test:
with my_table(startdate, enddate) as (
values
('2015-12-27 22:30', '2016-01-03 19:30'),
('2016-01-01 12:45', '2016-02-09 18:30')
)
select *
from my_table,
lateral get_months(startdate::date, enddate::date)
startdate | enddate | mon | year | days
------------------+------------------+-----+------+------
2015-12-27 22:30 | 2016-01-03 19:30 | Dec | 2015 | 5
2015-12-27 22:30 | 2016-01-03 19:30 | Jan | 2016 | 3
2016-01-01 12:45 | 2016-02-09 18:30 | Jan | 2016 | 31
2016-01-01 12:45 | 2016-02-09 18:30 | Feb | 2016 | 9
(4 rows)

Oracle count days per month

I wrote this SQL statement to calculate the days for each month
(select count(*) DAYs FROM
(
select trunc(ADD_MONTHS(sysdate,-1),'MM') + level -1 Dates from dual connect by
level <= ADD_MONTHS(trunc(sysdate,'MM'),1)-1 - trunc(sysdate,'MM')+1
) Where To_char(dates,'DY') NOT IN ('SA','SO'))
At the moment this statement ignores Saturdays and Sundays and it calculates the days from the month before the sysdate (June).
June has 22 days without weekends but sadly my statement says it has 23. I found out it includes the 1st July, which is wrong.
Do you know how I can tell my little statement it only calculates the days from the month I want to get not including days from another month?
Doing this sort of thing is always going to look not pretty... here's one way, which does it for the entire current year. You can restrict to a single month by adding an additional statement to the where clause:
select to_char(trunc(sysdate, 'y') + level - 1, 'fmMON') as month, count(*)
from dual
where to_char(trunc(sysdate, 'y') + level - 1, 'fmDY', 'nls_date_language=english') not in ('SAT','SUN')
connect by level <= trunc(add_months(sysdate, 12), 'y') - trunc(sysdate, 'y')
group by to_char(trunc(sysdate, 'y') + level - 1, 'fmMON')
As I said, not pretty.
Note a couple of things:
Use of the fm format model modifier to remove leading spaces
Explicit use of nls_date_language to ensure it'll work in all environments
I've added 12 months to the current date and then truncated it to the first of January to get the first day of the new year for simplicity
If you want to do this by month it might be worth looking at the LAST_DAY() function
The same statement (using LAST_DAY()) for the previous month only would be:
select count(*)
from dual
where to_char(trunc(sysdate, 'y') + level - 1, 'fmDY', 'nls_date_language=english') not in ('SAT','SUN')
connect by level <= last_day(add_months(trunc(sysdate, 'mm'), -1)) - add_months(trunc(sysdate, 'mm'), -1) + 1
Firstly, your inner query (select trunc(ADD_MONTHS(sysdate,-1),'MM') + level -1 Dates from dual connect by level <= ADD_MONTHS(trunc(sysdate,'MM'),1)-1 - trunc(sysdate,'MM')+1) returns the days of the month plus one extra day from the next month.
Secondly, a simpler query could use the LAST_DAY function which gets the last day of the month.
Finally, use the 'D' date format to get the day of the week as a number.
SELECT COUNT(*) FROM (
SELECT TO_CHAR(TRUNC(SYSDATE,'MM') + ROWNUM - 1, 'D') d
FROM dual CONNECT BY LEVEL <= TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'))
) WHERE d BETWEEN 1 AND 5;
Without having to generate all days of the month and then count them:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE FUNCTION WORK_DAYS_IN_MONTH(
dt DATE
) RETURN NUMBER DETERMINISTIC
AS
first_day DATE := TRUNC( dt, 'MM' );
remainder NUMBER := LAST_DAY( dt ) - ( first_day + INTERVAL '27' DAY );
BEGIN
RETURN 20 + CASE first_day - TRUNC( first_day, 'IW' )
WHEN 0 THEN remainder -- Monday
WHEN 1 THEN remainder -- Tuesday
WHEN 2 THEN remainder -- Wednesday
WHEN 3 THEN LEAST( remainder, 2 ) -- Thursday
WHEN 4 THEN LEAST( remainder, 1 ) -- Friday
WHEN 5 THEN GREATEST( remainder-2, 0 ) -- Saturday
ELSE GREATEST( remainder-1, 0 ) -- Sunday
END;
END;
//
Query 1:
SELECT ADD_MONTHS( DATE '2014-12-01', LEVEL ) AS "Month",
WORK_DAYS_IN_MONTH( ADD_MONTHS( DATE '2014-12-01', LEVEL ) ) AS "# Work Days"
FROM DUAL
CONNECT BY LEVEL <= 12
Results:
| Month | # Work Days |
|-----------------------------|-------------|
| January, 01 2015 00:00:00 | 22 |
| February, 01 2015 00:00:00 | 20 |
| March, 01 2015 00:00:00 | 22 |
| April, 01 2015 00:00:00 | 22 |
| May, 01 2015 00:00:00 | 21 |
| June, 01 2015 00:00:00 | 22 |
| July, 01 2015 00:00:00 | 23 |
| August, 01 2015 00:00:00 | 21 |
| September, 01 2015 00:00:00 | 22 |
| October, 01 2015 00:00:00 | 22 |
| November, 01 2015 00:00:00 | 21 |
| December, 01 2015 00:00:00 | 23 |

How to find 4th Saturday of month? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
Please let me know that how to find fourth Saturday of month in oracle. suppose we put "03/02/1999" then write a sql query to find out fourth saturday of month.
In Oracle you can use this:
NEXT_DAY(TRUNC(TO_DATE('1999-02-03','yyyy-mm-dd'), 'MM')-1, 'Saturday')+3*7
TRUNC( date, [fmt] ) takes an optional second argument which determines what time portion you are truncating to; so:
TRUNC( your_date, 'mm' )
Will truncate to the first day of the month and if you subtract a day from it then you get the last day of the previous month:
TRUNC( your_date, 'mm' ) - INTERVAL '1' DAY
or, more simply,
TRUNC( your_date, 'mm' ) - 1
NEXT_DAY( date, char) will get the next day, as specified by the second argument, from the given date. Combining the two functions will give you the first specified day of the month:
NEXT_DAY( TRUNC( your_date, 'mm' ) - 1, day_of_week )
Adding two weeks to it will give you the fourth day-of-the-week of the month:
NEXT_DAY( TRUNC( your_date, 'mm' ) - 1, 'Saturday' ) + INTERVAL '21' DAY
As an example:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE dates ( your_date ) AS
SELECT TO_DATE( '2014-' || LPAD( LEVEL, 2, '0' ) || '-01', 'YYYY-MM-DD' )
FROM DUAL
CONNECT BY LEVEL < 12
/
Query 1:
SELECT NEXT_DAY( TRUNC( your_date, 'mm') - INTERVAL '1' DAY, 'Saturday') + INTERVAL '21' DAY AS fourth_saturday
FROM dates
Results:
| FOURTH_SATURDAY |
|----------------------------------|
| January, 25 2014 00:00:00+0000 |
| February, 22 2014 00:00:00+0000 |
| March, 22 2014 00:00:00+0000 |
| April, 26 2014 00:00:00+0000 |
| May, 24 2014 00:00:00+0000 |
| June, 28 2014 00:00:00+0000 |
| July, 26 2014 00:00:00+0000 |
| August, 23 2014 00:00:00+0000 |
| September, 27 2014 00:00:00+0000 |
| October, 25 2014 00:00:00+0000 |
| November, 22 2014 00:00:00+0000 |
You could also wrap the code in a (more generic) function:
CREATE FUNCTION nth_day_of_month (
month DATE,
day_of_week VARCHAR2,
nth NUMBER
) RETURN DATE DETERMINISTIC
IS
BEGIN
RETURN NEXT_DAY( TRUNC( month, 'mm' ) - INTERVAL '1' DAY, day_of_week ) + (nth - 1) * 7;
END nth_day_of_month;
/
Query 2:
SELECT TO_CHAR( nth_day_of_month( your_date, 'Monday', 1 ), 'DAY YYYY-MM-DD' ) AS first_monday,
TO_CHAR( nth_day_of_month( your_date, 'Saturday', 4 ), 'DAY YYYY-MM-DD' ) AS fourth_saturday
FROM dates
Results:
| FIRST_MONDAY | FOURTH_SATURDAY |
|-------------------|---------------------|
| MONDAY 2014-01-06 | SATURDAY 2014-01-25 |
| MONDAY 2014-02-03 | SATURDAY 2014-02-22 |
| MONDAY 2014-03-03 | SATURDAY 2014-03-22 |
| MONDAY 2014-04-07 | SATURDAY 2014-04-26 |
| MONDAY 2014-05-05 | SATURDAY 2014-05-24 |
| MONDAY 2014-06-02 | SATURDAY 2014-06-28 |
| MONDAY 2014-07-07 | SATURDAY 2014-07-26 |
| MONDAY 2014-08-04 | SATURDAY 2014-08-23 |
| MONDAY 2014-09-01 | SATURDAY 2014-09-27 |
| MONDAY 2014-10-06 | SATURDAY 2014-10-25 |
| MONDAY 2014-11-03 | SATURDAY 2014-11-22 |
select next_day(trunc(to_date('1999-02- 03','yyyy-mm-dd'),'mm')-1,'saturday')+21
from dual;
Try this
DECLARE #StartDate DATETIME;
SELECT #StartDate = DATEADD(year, DATEDIFF(year,0,GETDATE()), 0);
-- This clause is just to get some numbers: a Tally table, for joining to get a range of dates
WITH NumDays AS (
SELECT TOP 365 -- one year of days
ROW_NUMBER() OVER (ORDER BY sc1.ID) AS N
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
UNION SELECT 0 -- just to get a zero in the results
), DateRange AS (
SELECT N,
DATEADD(day, N, #StartDate) AS [Date],
DATEPART(dw,DATEADD(day, N, #StartDate)) AS [Day],
DATENAME(dw,DATEADD(day, N, #StartDate)) AS [DayName],
DATENAME(week,DATEADD(day, N, #StartDate)) AS [WeekNo]
FROM NumDays
)
SELECT *
FROM DateRange
WHERE [DayName] = 'Saturday'
AND WeekNo in (4)
Here you can give weekNo comma separated. Suppose you want find 2nd and 4th Saturday then add this WeekNo in (2,4) instead of WeekNo in (4)

Get the difference between two dates both In Months and days in sql

I need to get the difference between two dates say if the difference is 84 days, I should probably have output as 2 months and 14 days, the code I have just gives the totals. Here is the code
SELECT Months_between(To_date('20120325', 'YYYYMMDD'),
To_date('20120101', 'YYYYMMDD'))
num_months,
( To_date('20120325', 'YYYYMMDD') - To_date('20120101', 'YYYYMMDD') )
diff_in_days
FROM dual;
Output is:
NUM_MONTHS DIFF_IN_DAYS
2.774193548 84
I need for example the output for this query to be either 2 months and 14 days at worst, otherwise I won't mind if I can have the exact days after the months figure because those days are not really 14 because all months do not have 30 days.
select
dt1, dt2,
trunc( months_between(dt2,dt1) ) mths,
dt2 - add_months( dt1, trunc(months_between(dt2,dt1)) ) days
from
(
select date '2012-01-01' dt1, date '2012-03-25' dt2 from dual union all
select date '2012-01-01' dt1, date '2013-01-01' dt2 from dual union all
select date '2012-01-01' dt1, date '2012-01-01' dt2 from dual union all
select date '2012-02-28' dt1, date '2012-03-01' dt2 from dual union all
select date '2013-02-28' dt1, date '2013-03-01' dt2 from dual union all
select date '2013-02-28' dt1, date '2013-04-01' dt2 from dual union all
select trunc(sysdate-1) dt1, sysdate from dual
) sample_data
Results:
| DT1 | DT2 | MTHS | DAYS |
----------------------------------------------------------------------------
| January, 01 2012 00:00:00 | March, 25 2012 00:00:00 | 2 | 24 |
| January, 01 2012 00:00:00 | January, 01 2013 00:00:00 | 12 | 0 |
| January, 01 2012 00:00:00 | January, 01 2012 00:00:00 | 0 | 0 |
| February, 28 2012 00:00:00 | March, 01 2012 00:00:00 | 0 | 2 |
| February, 28 2013 00:00:00 | March, 01 2013 00:00:00 | 0 | 1 |
| February, 28 2013 00:00:00 | April, 01 2013 00:00:00 | 1 | 1 |
| August, 14 2013 00:00:00 | August, 15 2013 05:47:26 | 0 | 1.241273 |
Link to test: SQLFiddle
Updated for correctness. Originally answered by #jen.
with DATES as (
select TO_DATE('20120101', 'YYYYMMDD') as Date1,
TO_DATE('20120325', 'YYYYMMDD') as Date2
from DUAL union all
select TO_DATE('20120101', 'YYYYMMDD') as Date1,
TO_DATE('20130101', 'YYYYMMDD') as Date2
from DUAL union all
select TO_DATE('20120101', 'YYYYMMDD') as Date1,
TO_DATE('20120101', 'YYYYMMDD') as Date2
from DUAL union all
select TO_DATE('20130228', 'YYYYMMDD') as Date1,
TO_DATE('20130301', 'YYYYMMDD') as Date2
from DUAL union all
select TO_DATE('20130228', 'YYYYMMDD') as Date1,
TO_DATE('20130401', 'YYYYMMDD') as Date2
from DUAL
), MONTHS_BTW as (
select Date1, Date2,
MONTHS_BETWEEN(Date2, Date1) as NumOfMonths
from DATES
)
select TO_CHAR(Date1, 'MON DD YYYY') as Date_1,
TO_CHAR(Date2, 'MON DD YYYY') as Date_2,
NumOfMonths as Num_Of_Months,
TRUNC(NumOfMonths) as "Month(s)",
ADD_MONTHS(Date2, - TRUNC(NumOfMonths)) - Date1 as "Day(s)"
from MONTHS_BTW;
SQLFiddle Demo :
+--------------+--------------+-----------------+-----------+--------+
| DATE_1 | DATE_2 | NUM_OF_MONTHS | MONTH(S) | DAY(S) |
+--------------+--------------+-----------------+-----------+--------+
| JAN 01 2012 | MAR 25 2012 | 2.774193548387 | 2 | 24 |
| JAN 01 2012 | JAN 01 2013 | 12 | 12 | 0 |
| JAN 01 2012 | JAN 01 2012 | 0 | 0 | 0 |
| FEB 28 2013 | MAR 01 2013 | 0.129032258065 | 0 | 1 |
| FEB 28 2013 | APR 01 2013 | 1.129032258065 | 1 | 1 |
+--------------+--------------+-----------------+-----------+--------+
Notice, how for the last two dates, Oracle reports the decimal part of months (which gives days) incorrectly. 0.1290 corresponds to exactly 4 days with Oracle considering 31 days in a month (for both March and April).
I think that your question is not defined well enough, for the following reason.
Answers relying on months_between have to deal with the following issue: that the function reports exactly one month between 2013-02-28 and 2013-03-31, and between 2013-01-28 and 2013-02-28, and between 2013-01-31 and 2013-02-28 (I suspect that some answerers have not used these functions in practice, or are now going to have to review some production code!)
This is documented behaviour, in which dates that are both the last in their respective months or which fall on the same day of the month are judged to be an integer number of months apart.
So, you get the same result of "1" when comparing 2013-02-28 with 2013-01-28 or with 2013-01-31, but comparing it with 2013-01-29 or 2013-01-30 gives 0.967741935484 and 0.935483870968 respectively -- so as one date approaches the other the difference reported by this function can increase.
If this is not an acceptable situation then you'll have to write a more complex function, or just rely on a calculation that assumes 30 (for example) days per month. In the latter case, how will you deal with 2013-02-28 and 2013-03-31?
is this what you've ment ?
select trunc(months_between(To_date('20120325', 'YYYYMMDD'),to_date('20120101','YYYYMMDD'))) months,
round(To_date('20120325', 'YYYYMMDD')-add_months(to_date('20120101','YYYYMMDD'),
trunc(months_between(To_date('20120325', 'YYYYMMDD'),to_date('20120101','YYYYMMDD'))))) days
from dual;
Here I'm just doing the difference between today, and a CREATED_DATE DATE field in a table, which obviously is a date in the past:
SELECT
((FLOOR(ABS(MONTHS_BETWEEN(CREATED_DATE, SYSDATE))) / 12) * 12) || ' months, ' AS MONTHS,
-- we take total days - years(as days) - months(as days) to get remaining days
FLOOR((SYSDATE - CREATED_DATE) - -- total days
(FLOOR((SYSDATE - CREATED_DATE)/365)*12)*(365/12) - -- years, as days
-- this is total months - years (as months), to get number of months,
-- then multiplied by 30.416667 to get months as days (and remove it from total days)
FLOOR(((SYSDATE - CREATED_DATE)/365)*12 - (FLOOR((SYSDATE - CREATED_DATE)/365)*12)) * (365/12))
|| ' days ' AS DAYS
FROM MyTable
I use (365/12), or 30.416667, as my conversion factor because I'm using total days and removing years and months (as days) to get the remainder number of days. It was good enough for my purposes, anyway.
The solution I post will consider a month with 30 days
select CONCAT (CONCAT (num_months,' MONTHS '), CONCAT ((days-(num_months)*30),' DAYS '))
from (
SELECT floor(Months_between(To_date('20120325', 'YYYYMMDD'),
To_date('20120101', 'YYYYMMDD')))
num_months,
( To_date('20120325', 'YYYYMMDD') - To_date('20120101', 'YYYYMMDD') )
days
FROM dual);
Find out Year - Month- Day between two Days in Orale Sql
select
trunc(trunc(months_between(To_date('20120101', 'YYYYMMDD'),to_date('19910228','YYYYMMDD')))/12) years ,
trunc(months_between(To_date('20120101', 'YYYYMMDD'),to_date('19910228','YYYYMMDD')))
-
(trunc(trunc(months_between(To_date('20120101', 'YYYYMMDD'),to_date('19910228','YYYYMMDD')))/12))*12
months,
round(To_date('20120101', 'YYYYMMDD')-add_months(to_date('19910228','YYYYMMDD'),
trunc(months_between(To_date('20120101', 'YYYYMMDD'),to_date('19910228','YYYYMMDD'))))) days
from dual;
SELECT (MONTHS_BETWEEN(date2,date1) + (datediff(day,date2,date1))/30) as num_months,
datediff(day,date2,date1) as diff_in_days FROM dual;
// You should replace date2 with TO_DATE('2012/03/25', 'YYYY/MM/DD')
// You should replace date1 with TO_DATE('2012/01/01', 'YYYY/MM/DD')
// To get you results
See the query below (assumed #dt1 >= #dt2);
Declare #dt1 datetime = '2013-7-3'
Declare #dt2 datetime = '2013-5-2'
select abs(DATEDIFF(DD, #dt2, #dt1)) Days,
case when #dt1 >= #dt2
then case when DAY(#dt2)<=DAY(#dt1)
then Convert(varchar, DATEDIFF(MONTH, #dt2, #dt1)) + CONVERT(varchar, ' Month(s) ') + Convert(varchar, DAY(#dt1)-DAY(#dt2)) + CONVERT(varchar, 'Day(s).')
else Convert(varchar, DATEDIFF(MONTH, #dt2, #dt1)-1) + CONVERT(varchar, ' Month(s) ') + convert(varchar, abs(DATEDIFF(DD, #dt1, DateAdd(Month, -1, #dt1))) - (DAY(#dt2)-DAY(#dt1))) + CONVERT(varchar, 'Day(s).')
end
else 'See asumption: #dt1 must be >= #dt2'
end In_Months_Days
Returns:
Days | In_Months_Days
62 | 2 Month(s) 1Day(s).