Oracle Months Between does not work properly - sql

Good morning,
I've wrote the following query to get the months difference between a date column (STRT_DT) and a timestamp column (VLD_FRM_TMS) stored in two different tables.
I don't know why it works for some records but does not for others (it calculates one month less)
select ID, floor (months_between (cast(a.VLD_FRM_TMS as date), STRT_DT)) as delta
from TABLE_A a
inner join TABLE_B b
on a.ID = b.ID
This is an example of record for which the calculation does not work:
VLD_FRM_TMS
-----------
28-FEB-21 12.00.00.000000000 AM
STRT_DT
--------
29-OCT-20
The formula calculates 3 months instead of 4...
Could anyone help me in locating the problem?
Thank you in advance

This is exactly the behavior that is described in the documentation:
If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise Oracle Database calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.
(Note: Highlighting is mine.)
If you run this code:
select months_between(date '2021-02-28', date '2020-10-29') as delta
from dual
The result is 3.9677.
I suspect that you want some other logic. However, the question does not specify what logic you actually do want.

I actually want the months difference regardless of the specific day of the month.
You can truncate both values to the first of the month using the 'MM' format element, and then get the difference between those:
months_between (trunc(a.VLD_FRM_TMS, 'MM'), trunc(STRT_DT, 'MM')) as delta
That will now always be an integer - i.e. a whole number of months - so you don't need to trunc/floor/round the result.
db<>fiddle showing the problem with the old calculation and this version.

Related

Modifying SYSDATE function

In one of my SQL queries, I am using
[... and z.READ_TIMESTAMP > TIMESTAMP_TO_EPOCH(TRUNC(SYSDATE-3)]
If I want the date to be exactly 5/31/2017, will I use 'SYSDATE' (date-n) function or some other expression? or how can a modify my query for 5/31/2017
If you want the date to be exactly 5/31/2017 then use TO_DATE() or TO_TIMESTAMP() depending on which data type you need (date or timestamp). As you are using SYSDATE already the the date data type should work.
-- e.g.
select
to_date('5/31/2017','mm/dd/yyyy')
, to_timestamp('5/31/2017','mm/dd/yyyy')
from dual
...
and z.READ_TIMESTAMP > TIMESTAMP_TO_EPOCH(to_date('5/31/2017','mm/dd/yyyy'))
HOWEVER
I suspect you may want more than just a way to establish a fixed date. For example are you asking for "how do I get that last day of the previous month?" which perhaps can be satisfied by using >= and the first day of current month like this:
...
and z.READ_TIMESTAMP >= TIMESTAMP_TO_EPOCH(trunc(sysdate,'MM'))
or if it really is the last day of the previous month can be achieved with a combination of LAST_DAY() and ADD_MONTHS()
and z.READ_TIMESTAMP >
TIMESTAMP_TO_EPOCH( last_day(add_months(trunc(sysdate,'MM'),-1)) )
Without knowing a great deal more about the nature of your data and query purpose please do note that each date you use when "truncated" also has the time set to 00:00:000 - so IF you data contains time within a day other than 00:00:00 then these 2 queries might NOT produce the same result
.... datetimecolumn > to_date('05/31/2017','mm/dd/yyyy') -- "a"
.... datetimecolumn >= to_date('06/01/2017','mm/dd/yyyy') -- "b"
For example "a" the entire 24 hour duration of 05/31/2017 would be included in the results, but for example "b" that same 24 hour duration would be excluded from results. In my experience the last day of any month isn't really the best method for locating date/time based data, instead usually it is the first day of the next month that produces the correct result.

Oracle SQL: Dynamic timeframe calculation

Greetings all knowing Stack.
I am in a bit of a pickle, and I am hoping for some friendly assistance form the hive mind.
I need to write a query that returns the difference in days between a registration date (stored in a table column) and the first day of the last September.
For example; assuming the query was being run today (24-10-2016) for a record with a registration date of 14-07-2010, I would want the script to return the difference in days between 14-07-2010 and 01-09-2016
However had I run the same query before the end of last August, for example on 12-08-2016, I would want the script to return the difference in days between 14-07-2010 and 01-09-2015.
I'm fine with the process of calculating differences between dates, it's just the process of getting the query to return the 'first day of the last September' into the calculation that is tripping me up!
Any input provided would be much appreciated.
Thankyou =)
Try this approach:
add four months to the current date
truncate this date to the first of year
subtract four months again
Add_Months(Trunc(Add_Months(SYSDATE, 4), 'year'), -4)
Hope this might help.
WITH T AS (SELECT TO_DATE('14-07-2010','DD-MM-YYYY') REG_DATE,
SYSDATE EXEC_DATE
FROM DUAL)
SELECT CASE WHEN TO_CHAR(EXEC_DATE,'MM') >= 9
THEN ADD_MONTHS(TRUNC(EXEC_DATE,'YEAR'),8)
ELSE ADD_MONTHS(TRUNC(ADD_MONTHS(EXEC_DATE,-12),'YEAR'),8)
END
- REG_DATE AS DIFF
FROM T;

How to get records between from and to date, when dates are for the same month/year?

I am trying to create a query to that can get some records in a table that is between a from and to date, with the dates being in month/year only. The problem that I am having is trying to get the records when the from and to dates are for the same month/year.
Here is a example of the issue that I am having:
select start_date
from job
where trunc(start_date) between to_date('05-2016','mm-yyyy') and to_date('05-2016','mm-yyyy')
In the job table, there are records with start_date in the month of May, but in order to see them I need to set the to date to '06-2016'. Is there way to get all of the records with a start_date in the month of May by just specifying that the from and to dates is 05-2016?
In your example you are selecting all start_date's between 5/1/2016 and 5/1/2016.
It seems like you want to capture everything in a month, but you are not specifying a format to truncate. Without specifying 'Month' in the Trunc() you are truncating to the day. When you truncate to Month you can now, actually just set it equal to the to_date() rather than between:
select start_date
from job
where trunc(start_date,'Month') = to_date('05-2016','mm-yyyy')
Here is some information on Trunc(date, [fmt]). when the fmt argument is left blank Trunc() defaults to 'round' to the nearest day but there are many other options.
If you want to specify ranges greater than a Month you can use between (but note, this is from the first day of the first to_date() to the first day of the second 'to_date()':
select start_date
from job
where trunc(start_date)
between to_date('06-2016','mm-yyyy')
and to_date('09-2016','mm-yyyy')
In this example all Start_dates would populate between 6/1/2016 and 9/1/2016.
I think the most accurate way to do this would be:
select start_date
from job
where trunc(start_date)
between to_date('06-01-2016','mm-dd-yyyy')
and to_date('09-30-2016','mm-dd-yyyy')
How about?
select start_date
from job
where trunc(start_date, 'mm') between
to_date('05-2016', 'mm-yyyy')
and to_date('05-2016', 'mm-yyyy')
Oracle has no way of interpolating the date on the right end of your between as falling at the end of the month. When to_date() is missing a day value in the format it simply supplies the 1st as the default value. Not sure if that's the behavior your were anticipating.
If instead you truncate dates by month (rather than day) then the comparisons will only involve dates falling on the first of the month. With the dates collapsed that way you can then treat a start and end of the two identical values as an inclusive range. Presumably you'll replace the hard-coded dates with parameters of some form and this works for wider ranges as well. Of course, the between can be just an equality test if you're always querying on a single month. And this isn't going to use an index on your column so it's not necessarily the most efficient.
I think this is what you're looking for. Is there a reason you need to supply the date value(s) in that format? Perhaps there's a better way to approach this using a little date math.
Try like this:
select start_date
from job
where trunc(start_date) between to_date('05-2016','mm-yyyy')
and dateadd('d', -1, dateadd('m', 1, to_date('05-2016','mm-yyyy')))
In order to see an entire month worth of dates, your date range in the between clause must be a full month long.

Get the month and year now then count the number of rows that are older then 12 months in SQL/Classic ASP

I know this one is pretty easy but I've always had a nightmare when it comes to comparing dates in SQL please can someone help me out with this, thanks.
I need to get the month and year of now then compare it to a date stored in a DB.
Time Format in the DB:
2015-08-17 11:10:14.000
I need to compare the month and year with now and if its > 12 months old I will increment a count. I just need the number of rows where this argument is true.
I assume you have a datetime field.
You can use the DATEDIFF function, which takes the kind of "crossed boundaries", the start date and the end date.
Your boundary is the month because you are only interested in year and month, not days, so you can use the month macro.
Your start time is the value stored in the table's row.
Your end time is now. You can get system time selecting SYSDATETIME function.
So, assuming your table is called mtable and the datetime object is stored in its date field, you simply have to query:
SELECT COUNT(*) FROM mtable where DATEDIFF(month, mtable.date, (SELECT SYSDATETIME())) > 12

Calculate year from date difference in Oracle

I want to calculate the number of years between two dates.
eg :- Select to_date('30-OCT-2013') - TO_date('30-SEP-2014') FROM DUAL;
This would result to 335 days. I want to show this in years, which will be .97 years.
Simply do this(divide by 365.242199):
Select (to_date('30-SEPT-2014') - TO_date('30-OCT-2013'))/365.242199 FROM DUAL;
1 YEAR = 365.242199 days
OR
Try something like this using MONTHS_BETWEEN:-
select floor(months_between(date '2014-10-10', date '2013-10-10') /12) from dual;
or you may also try this:-
SELECT EXTRACT(YEAR FROM date1) - EXTRACT(YEAR FROM date2) FROM DUAL;
On a side note:-
335/365.242199 = 0.917199603 and not .97
I don't know how you figure that's .97 years. Here's what I get:
SQL> SELECT ( TO_date('30-SEP-2014') - to_date('30-OCT-2013')) /
(ADD_MONTHS(DATE '2013-10-30',12) - DATE '2013-10-30') "Year Fraction"
FROM DUAL;
Year Fraction
-------------
0.91780821917
You're going to have to pick a date to base your year calculation on. This is one way to do it. I chose to make a year be the number of days between 10/30/2013 and 10/30/2014. You could also make it a year between 9/30/2013 and 9/30/2014.
As an aside, if you're only interested in 2 decimal places, 365 is pretty much as good as 366.
UPDATE: Used ADD_MONTHS in calculating the denominator. That way you can use the same date for the entire calculation of the number of days in a year.
None of the methods proposed in the other answers give exactly the same answer, look:
with dates as ( select to_date('2013-10-01', 'YYYY-MM-DD') as date1, to_date('2014-09-01', 'YYYY-MM-DD') as date2 from dual)
select months_between(date2, date1)/12 as years_between, 'months_between(date1, date2)' as method from dates
union
select (date2 - date1)/365.242199, '(date2 - date1) / 365.242199' from dates
union
select extract(year from date2) - extract(year from date1), 'extract(year) from date2 - extract(year from date1)' from dates
union
select (date2 - date1) / (ADD_MONTHS(date1 ,12) - date1), '(nb days between date1 and date2) / (nb days in 1 year starting at date1)' from dates
;
gives
YEARS_BETWEEN METHOD
0.9166666666666666666666666666666666666667 months_between(date1, date2)
0.9171996032145234127231831719422979380321 (date2 - date1) / 365.242199
0.9178082191780821917808219178082191780822 nb days date2-date1 / (nb days in 1 year starting at date1)
1 extract(year) from date2 - extract(year from date1)
Why? Because they are all answering slightly different questions.
MONTHS_BETWEEN gives the number of whole months between the 2 dates, and calculates the fractional part as the remainder in days divided by 31.
dividing by 365.242199 assumes that you want the number of solar years between 00:00 on the first date and 00:00 on the second date, to 9 significant figures.
the third method assumes you want to calculate how many calendar days between the two dates, relative to the number of calendar days in the specific year that started on the first date (so the same number of calendar days will give you a different number of years, depending on whether there's a leap day between date1 and the same date on the following year).
the extract(year) approach assumes you want know the difference in whole numbers between the calendar year of the first date and the calendar year of the second date
It's not possible to answer the question perfectly, without knowing which kind of year we are talking about. Do we mean a solar year, or a calendar year, and if we mean a calendar year, do we we want to calculate by months (as if all months were the same length, which they aren't) or by the actual number of days between those dates and in that specific year?
Indeed, if we're talking about calendar years, it's not possible to calculate a fractional number of years in a consistent way at all, since the concept "calendar year" doesn't correspond to a fixed number of days.
The good news is that (aside from the fourth method) all the approaches give the same answer to the first 2 significant figures, as DCookie said. So you can save worrying about what you mean when you say "year", and instead start to think of other concerns such as performance, portability, readability... which also are quite different between these approaches.
I do think though, that whenever a non-programmer asks for something like "the fractional number of years between two dates," they should be punished by being given a detailed explanation of the different ways to calculate it, and why and how they are different, until they agree that it would be better expressed in number of weeks (which at least have the benefit of containing a fixed number of days).