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).
Related
In PL/SQL I have 2 dates and I need to find out the number of months between them and the days as well. For example date 1 is 1/10/2022 and date 2 is 2/12/2022 that would be 1 month and 2 days. I'm pretty secure in obtaining the number of months, but the days number has been a thorn in my side. Sometimes it comes out correct, sometimes it comes out short and other times it comes out too far. I would imagine it is because of the different number of days in the months, but I can't prove that just yet. Any help is appreciated.
Oracle provides a months_between function to do the calculation.
That isn't a good idea as the number of days in a month varies, it's not exactly known what the decimal part of the number represents.
select months_between(date '2022-04-03', date '2022-01-01') from dual;
MONTHS_BETWEEN(DATE'2022-04-03',DATE'2022-01-01')
3.06451612903225806451612903225806451613
If you assume every month has 30 days, then comparisons over larger date ranges (years) will be out by more and more days the larger the difference gets.
However, if you combine methods, using months_between to get the months, and then assume 30 days for a month to get the days part from the remainder, it's more consistent over longer periods…
with dates as (select date '2022-01-01' as date_from, date '2022-04-03' as date_to from dual)
select months_between(date_to, date_from) ,trunc(months_between(date_to, date_from)) as months ,round(mod(months_between(date_to, date_from),1)*30) as days
from dates
MONTHS_BETWEEN(DATE_TO,DATE_FROM) MONTHS DAYS
3.06451612903225806451612903225806451613 3 2
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.
I have 70.000 rows of data, including a date time column (YYYY-MM-DD HH24-MM-SS.).
I want to split this data into 3 separate columns; Hour, day and Week number.
The date time column name is 'REGISTRATIONDATE' from the table 'CONTRACTS'.
This is what I have so far for the day and hour columns:
SELECT substr(REGISTRATIONDATE, 0, 10) AS "Date",
substr(REGISTRATIONDATE, 11, 9) AS "Hour"
FROM CONTRACTS;
I have seen the options to get a week number for specific dates, this assignment concerns 70.000 dates so this is not an option.
You (the OP) still have to explain what week number to assign to the first few days in a year, until the first Monday of the year. Do you assign a week number for the prior calendar year? In a Comment I asked about January 1, 2017, as an example; that was a Sunday. The week from January 2 to January 8 of 2017 is "week 1" according to your definition; what week number do you assign to Sunday, January 1, 2017?
The straightforward calculation below assigns to it week number 0. Other than that, the computation is trivial.
Notes: To find the Monday of the week for any given date dt, we can use trunc(dt, 'iw'). iw stands for ISO Week, standard week which starts on Monday and ends on Sunday.
Then: To find the first Monday of the year, we can start with the date January 7 and ask for the Monday of the week in which January 7 falls. (I won't explain that one - it's easy logic and it has nothing to do with programming.)
To input a fixed date, the best way is with the date literal syntax: date '2017-01-07' for January 7. Please check the Oracle documentation for "date literals" if you are not familiar with it.
So: to find the week number for any date dt, compute
1 + ( trunc(dt, 'iw') - trunc(date '2017-01-07', 'iw') ) / 7
This formula finds the Monday of the ISO Week of dt and subtracts the first Monday of the year - using Oracle date arithmetic, where the difference between two dates is the number of days between them. So to find the number of weeks we divide by 7; and to have the first Monday be assigned the number 1, instead of 0, we need to add 1 to the result of dividing by 7.
The other issue you will have to address is to convert your strings into dates. The best solution would be to fix the data model itself (change the data type of the column so that it is DATE instead of VARCHAR2); then all the bits of data you need could be extracted more easily, you would make sure you don't have dates like '2017-02-29 12:30:00' in your data (currently, if you do, you will have a very hard time making any date calculations work), queries will be a lot faster, etc. Anyway, that's an entirely different issue so I'll leave it out of this discussion.
Assuming your REGISTRATIONDATE if formatted as 'MM/DD/YYYY'
the simples (and the faster ) query is based ond to to_char(to_date(REGISTRATIONDATE,'MM/DD/YYYY'),'WW')
(otherwise convert you column in a proper date and perform the conversio to week number)
SELECT substr(REGISTRATIONDATE, 0, 10) AS "Date",
substr(REGISTRATIONDATE, 11, 9) AS "Hour",
to_char(to_date(REGISTRATIONDATE,'MM/DD/YYYY'),'WW') as "Week"
FROM CONTRACTS;
This is messy, but it looks like it works:
to_char(
to_date(RegistrationDate,'YYYY-MM-DD HH24-MI-SS') +
to_number(to_char(trunc(to_date(RegistrationDate,'YYYY-MM-DD HH24-MI-SS'),'YEAR'),'D'))
- 2,
'WW')
On the outside you have the solution previous given by others but using the correct date format. In the middle there is an adjustment of a certain number of days to adjust for where the 1st Jan falls. The trunc part gets the first of Jan from the date, the 'D' gets the weekday of 1st Jan. Since 1 represents Sunday, we have to use -2 to get what we need.
EDIT: I may delete this answer later, but it looks to me that the one from #mathguy is the best. See also the comments on that answer for how to extend to a general solution.
But first you need to:
Decide what to do dates in Jan before the first Monday, and
Resolve the underlying problems in the date which prevent it being converted to dates.
On point 1, if assigning week 0 is not acceptable (you want week 52/53) it gets a bit more complicated, but we'll still be able to help.
As I see it, on point 2, either there is something systematically wrong (perhaps they are timestamps and include fractions of a second) or there are isolated cases of invalid data.
Either the length, or the format, or the specific values don't compute. The error message you got suggests that at least some of the data is "too long", and the code in my comment should help you locate that.
I'm pretty new to SQL so I hope this isn't a dumb question, tried to google but couldn't find anything.
I'm summing sales of departments per week in SQL and am using TD_SYSFNLIB.WEEKNUMBER_OF_YEAR (trans_dt) to get the week number.
I think everything is working except I'd like to change the format of the weeks to the start date of the week, e.g. week 1 = 1/4/15
Also, i'm not sure how to handle the very first of the year week 0 since I think that should be grouped up with week 52 of last year.
The following date math trick should get you Beginning of Week as an actual date without having to join to the SYS_CALENDAR view or using a function:
SELECT CURRENT_DATE - ((CURRENT_DATE - DATE '0001-01-07) MOD 7) AS BOW;
Starting with TD14 there's NEXT_DAY which returns the following weekday, if you subtract 7 days you get the previous day:
next_day(trans_dt - 7, 'sunday')
I have two TIMESTAMP columns in my table: customer_birthday and purchase_date. I want to create a query to show the number of purchases by customer age, to create a chart.
But how do I calculate ages, in years, using BigQuery? In other words, how do I get the difference in years between two TIMESTAMPs? The age calculation cannot be made using days or hours, because of leap years, so the function DATEDIFF(<timestamp1>,<timestamp2>) is not appropriate.
Thanks.
First of all, I'd really love BigQuery to have a function which calculates current age based on a date. That seems to be like a very common use case and it's not really easy due to the whole leap year thing.
I found a great article about this issue: https://towardsdatascience.com/how-to-accurately-calculate-age-in-bigquery-999a8417e973
Their final approach is similar to Lars Haugseth's and Saad's answer, but they do not use the DAYOFYEAR part in order to avoid issues with leap years. It also gives you the flexibility not only to calculate the current age, but also the age at a particular date that you pass to the function as argument:
CREATE OR REPLACE FUNCTION workspace.age_calculation(as_of_date DATE, date_of_birth DATE)
AS (
DATE_DIFF(as_of_date,date_of_birth, YEAR) -
IF(EXTRACT(MONTH FROM date_of_birth)*100 + EXTRACT(DAY FROM date_of_birth) >
EXTRACT(MONTH FROM as_of_date)*100 + EXTRACT(DAY FROM as_of_date)
,1,0)
)
Regarding the difference between dates - you could consider user-defined functions (https://cloud.google.com/bigquery/user-defined-functions) with a JavaScript date library, such as Datejs or Moment.js
You can use DATE_DIFF to get the difference in years, but need to subtract by one if the birthday has not yet occured this year:
IF(EXTRACT(DAYOFYEAR FROM CURRENT_DATE) < EXTRACT(DAYOFYEAR FROM birthdate),
DATE_DIFF(CURRENT_DATE, birthdate, YEAR) - 1,
DATE_DIFF(CURRENT_DATE, birthdate, YEAR)) AS age
Here it is in a user defined function:
CREATE TEMP FUNCTION calculateAge(birthdate DATE) AS (
DATE_DIFF(CURRENT_DATE, birthdate, YEAR) +
IF(EXTRACT(DAYOFYEAR FROM CURRENT_DATE) < EXTRACT(DAYOFYEAR FROM birthdate), -1, 0) -- subtract 1 if bithdate has not yet occured this year
);
You can compute the number of days it would be if all years were 365 days long, take the difference, and divide by 365. For example:
SELECT (day2-day1)/365
FROM (
SELECT YEAR(t1) * 365 + DAYOFYEAR(t1) as day1,
YEAR(t2) * 365 + DAYOFYEAR(t2) as day2
FROM (
SELECT TIMESTAMP('20000201') as t1,
TIMESTAMP('20140201') as t2))
This returns 14.0, even though there are intervening leap years. If you want the final result as an integer instead of floating point, you can use the INTEGER() function to cast the result.
Note that if one of the dates is a leap day (feb 29) it will appear to be one year away from march 1, but I think this sounds like the intended behavior.
Another way to calculate age that takes leap years into account is to:
Calculate simple age based on difference in year
Either subtract 1 or not by:
Add difference in years to birthday (e.g. if today is 2022-12-14 and birthday is 2000-12-30, then the "new" birthday becomes 2022-12-30)
Do a DAY-based difference between today and "new" birthday, which either gives you a positive number (birthday passed for this year) or negative number (still has birthday this year)
Subtract 1 year from simple age calculation if number is negative
In BigQuery SQL code this looks like:
SELECT
bd AS birthday
,today
,DATE_DIFF(today, bd, YEAR) AS simpleAge
,DATE_DIFF(today, bd, YEAR) +
(CASE
WHEN DATE_DIFF(today, DATE_ADD(bd, INTERVAL DATE_DIFF(today, bd, YEAR) YEAR), DAY) >= 0
THEN 0
ELSE -1
END) AS age
FROM
(SELECT
PARSE_DATE("%Y-%m-%d", "2000-12-01") AS bd
,CURRENT_DATE("Asia/Tokyo") AS today
)
Outputs:
birthday
today
simpleAge
age
2000-12-30
2022-12-14
22
21