Get month,days difference between two date columns - sql

I'm trying to fix some problems in my database and i want to re-calculate column in my db based on other 2 date columns. This col is float and i want to get the difference between 2 dates in months with decimal point for days.
For example if i have 2 dates '2016-01-15', '2015-02-01' the difference should be 12.5 best of 12 months differences and 0.5 for the remaining 15 days
Here is what i tried so far based on my searches but i think there is something i'm missing as it tells me there is an error with my date col as it doesn't exist
Select EXTRACT(year FROM vehicle_delivery(date, vehicle_received_date))*12 + EXTRACT(month FROM vehicle_delivery(date, vehicle_received_date));
Where vehicle_delivery is my table name & date is my end date and vehicle_received_date is my start date
same thing happes with this sql :
select extract('years' from vehicle_delivery) * 12 + extract('months' from vehicle_delivery) + extract('days' from vehicle_delivery) / 30
from (select age(date::timestamp, vehicle_received_date::timestamp)) a;

The SQL should look like this:
select extract(year from diff) * 12 + extract(month from diff) + extract(day from diff) / 30
from (select age(date::timestamp, vehicle_received_date::timestamp) as diff
from vehicle_delivery
) vd;
I don't know what the purpose of the / 30 is, but you appear to want it.
Notes:
The FROM clause references the table.
The first argument in extract() is a keyword, not a string.
You want to reference the age() value in the extract().
extract() returns an interval, so it is rather redundant to take out the parts (only needed if you want them in separate columns).

Related

Convert integer years or months into days in SQL impala

I have two columns; both have integer values. One Representing years, and the other representing months.
My goal is to perform calculations in days (integer), so I have to convert both to calendar days, to achieve that, taking in consideration that we have years with both 365 and 366 days.
Example in pseudo code:
Select Convert(years_int) to days, Convert(months int) to days
from table.
Real Example:
if --> Years = 1 and Months = 12
1) Convert both to days to compare them: Years = 365 days; Months = 365 days
After conversion : (Years = Months) Returns TRUE.
The problem is when we have years = 10 (for example), we must take in account the fact that at least two of them have 366 days. The same with Months - we have 30 and 31 days. So I need to compensate that fact to get the most accurate possible value in days.
Thanks in advance
From integers to timestamp can be done in PostgreSQL. I do not have impala, but hopefully below script will help you getting this done using impala:
with
year as (select 2022 as y union select 2023),
month as (select generate_series(1,12) as m),
day as(select generate_series(1,31) as d )
select y,m,d,dt from (
select
y,m,d,
to_date(ds,'YYYYMMDD')+(((d-1)::char(2))||' day')::interval dt
from ( select
*,
y::char(4)|| right('0'||m::char(2),2) || right('0'||0::char(2),2) as ds
from year,month,day
) x
) y
where extract(year from dt)=y and extract(month from dt)=m
order by dt
;
see: DBFIDDLE
Used functions in this query and, a way, to convert them to imapala (remember I do not use that tool/language/dialect)
function
impala alternative
to_date(a,b)
This will convert the string a to a date using the format b. Using impala you can use CAST(expression AS type FORMAT pattern)
y::char(4)
Cast y to a char(4), Using imala you can use: CAST(expression AS type)
right(a,b)
Use: right()
\\
Use: concat()
generate_series(a,b)
This generates a serie of numbers from a to (an inclusing) b. A SQL altervative is to write SELECT 1 as x union SELECT 2 union SELECT 3, which generates the same series as generate_series(1,3) in PostgreSQL
extract(year from a)
Get the year from the datetime field a, see YEAR()
One special case is this one to_date(ds,'YYYYMMDD')+(((d-1)::char(2))||' day')::interval
This will convert ds (with datatype CHAR(8)) to a date, and then add (using +) a number of days (like: '4 day')
Because I included all days until 31, this will fail in Februari, April, June, September, November because those months do not have 31 days. This is corrected by the WHERE clause in the end (where extract(year from dt)=y and extract(month from dt)=m)

DB2 How to select dates for the previous 2 months

I am using Oracle SQL developer on DB2 and have a date field stored as an integer e.g. 20210401
I want to bring back results for the last 2 months and have tried this:
select * from table where date > add_months(sysdate, -2)
This is producing error 206 saying it is not valid in the context used.
Does anyone know how to convert the data column or have an easier way to filter for the last 2 months
Use this:
select *
from table
where date > INT (TO_CHAR (CURRENT TIMESTAMP - 2 MONTH, 'YYYYMMDD'));

Presto SQL get yyyymm minus 2 months

I am using Presto. I have an integer column (let's call the column 'mnth_nbr') showing year and month as: yyyymm. For instance, 201901. I want to have records showing all dates AFTER 201901 as well as 2 months before the given date. In this example, it would return 201811, 201812, 201901, 201902, 201903, etc. Keep in mind that my data type here is integer.
This is what I have so far (I do a self join):
select ...
from table 1 as first_table
left join table 1 as second_table
on first_table.mnth_nbr = second_table.mnth_nbr
where first_table.mnth_nbr <= second_table.mnth_nbr
I know this gives me all dates AFTER 201901, including 201901. But, I don't know how to add the 2 previous months (201811 and 201812)as explained above.
As far as the documentation, Presto DB date_parse function expects a MySQL-like date format specifier.
So the proper condition for your use case should be :
SELECT ...
FROM mytable t
WHERE
date_parse(cast(t.mnth_nbr as varchar), '%Y%m') >= date '2019-01-01' - interval '2' month
Edit
As commented by Piotr, a more optimized expression (index-friendly) would be :
WHERE
mnth_nbr >= date_format(date '2019-01-01' - interval '2', '%Y%m')
Something like this would help. first parse your int to date
date_parse(cast(first_table.mnth_nbr as varchar), 'yyyymm') > date '2019-01-01' - interval '2' month
please keep in mind that you may encounter with indexing issues with this approach.

SQL computing and reusing fiscal year calculation in sql query

I have a condition in my SQL query, using Oracle 11g database, that depends on a plan starting or ending with in a fiscal year:
(BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) OR
(BUSPLAN.END_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31'))
For now, I am passing in YEAR as a parameter. It can be computed as (pseudocode):
IF CURRENT MONTH IN (JAN, FEB, MAR):
USE CURRENT YEAR // e.g. 2015
ELSE:
USE CURRENT YEAR + 1 // e.g. 2016
Is there a way I could computer the :YEAR parameter within in an SQL query and reuse it for the :YEAR parameter?
CTEs are easy, you can make little tables on the fly. With a 1 row table you just cross join it and then you have that value available every row:
WITH getyear as
(
SELECT
CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1
END as ynum from dual
), mydates as
(
SELECT getyear.ynum || '-04-01' as startdate,
getyear.ynum+1 || '-03-31' as enddate
from getyear
)
select
-- your code here
from BUSPLAN, mydates -- this is a cross join
where
(BUSPLAN.START_DATE BETWEEN mydates.startdate AND mydates.enddate) OR
(BUSPLAN.END_DATE BETWEEN mydates.startdate AND mydates.enddate)
note, values statement is probably better if Oracle has values then the first CTE would look like this:
VALUES(CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1)
I don't have access to Oracle so I might have bugs typos etc since I didn't test.
In the code you shared there is a problem and a potential problem.
Problem, implicit conversion to date without format string.
In (BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) two strings are being formed and then converted to dates. The conversion to date is going to change depending on the value of NLS_DATE_FORMAT. To insure that the string is converted correctly to_date(:YEAR || '-04-01', 'YYYY-MM-DD').
Potential problem, boundary at the end of the year when time <> midnight.
Oracle's date type holds both date and time. A test like someDate between startDate and endDate will miss all records that happened after midnight on endDate. One simple fix that precludes use of indexes on someDate is trunc(someDate) between startDate and endDate.
A more general approach is to define date ranges and closed open intervals. lowerBound <= aDate < upperBound where lowerBound is the same asstartDateabove andupperBoundisendDate` plus one day.
Note: Some applications used Oracle date columns as dates and always store midnight, if your application is of that sort, then this is not a problem. And check constraints like check (trunc(dateColumn) = dateColumn) would make sure it stays that way.
And now, to answer the question actually asked.
Using subquery factoring (Oracle's terminology) / common table expression (SQL Server's terminology) one can avoid repetition within a query.
Instead of figuring out the proper year, and then using strings to put together dates, the code below starts by getting January 1 at Midnight of the current calendar year, trunc(sysdate, 'YEAR')). Then it adds an offset in months. When the months are Jan, Feb, Mar, the current fiscal year started last year on 4/1, or nine months before the start of this year. The offset is -9. Else the current fiscal year started 4/1 of this calendar year, start of this year plus three months.
Instead of end date, an upper bound is calculated, similar to lower bound, but with the offsets being 12 greater than lower bound to get 4/1 the following year.
with current_fiscal_year as (select add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then -9 else 3 end) as LowerBound
, add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then 3 else 15 end) as UpperBound
from dual)
select *
from busplan
cross join current_fiscal_year CFY
where (CFY.LowerBound <= busplan.start_date and busplan.start_date < CFY.UpperBound)
or (CFY.LowerBound <= busplan.end_date and busplan.end_date < CFY.UpperBound)
And yet more unsolicited advise.
The times I've had to deal with fiscal year stuff, avoiding repetition within a query was low hanging fruit. Having the fiscal year calculations consistent and correct among many queries, that was the essence of the work. So I'd recommend a developing PL/SQL package that centralizes fiscal calculations. It might include a function like:
create or replace function GetFiscalYearStart(v_Date in date default sysdate)
return date
as begin
return add_months(trunc(v_Date, 'YEAR')
, case when extract(month from v_Date) <= 3 then -9 else 3 end);
end GetFiscalYearStart;
Then the query above becomes:
select *
from busplan
where (GetFiscalYearStart() <= busplan.start_date
and busplan.start_date < add_months(GetFiscalYearStart(), 12))
or (GetFiscalYearStart() <= busplan.end_date
and busplan.end_date < add_months(GetFiscalYearStart(), 12))

AS400 SQL query to determine records for who is 70.5 years old for the current year

I am trying to find individuals that will turn 70.5 years old in the current year.
dob7 = DECIMAL(7) YYYYDDD
select acctno, name, address, status, year(curdate()) - year(date(digits(dob7))) as Age
from mydata.cdmast cdmast
left join mydata.cfmast cfmast
on cdmast.cifno = cfmast.cifno
where status <> 'R' and year(curdate()) - year(date(digits(dob7))) >= 70
The code above returns the following error:
[Error Code: -181, SQL State: 22008] [IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0181 - Value in date, time, or timestamp string not valid.
After seeing the other answers, I'm submitting my own. This should have the benefit of using any indicies on dob7, and should work without too many 'tricks'.
I've modified the WHERE clause in your original query. I'm assuming '.5 years' means '6 months', although this is adjustable. I deliberately wrapped the calculations in CTEs to 'encapsulate' the logic; the operations should be nearly no-cost.
WITH Youngest (dateOfBirth) as (
SELECT CURRENT_DATE - 70 YEARS - 6 MONTHS
FROM sysibm/sysdummy1),
Converted (dateOfBirth, formatted) as (
SELECT dateOfBirth, YEAR(dateOfBirth) * 1000 + DAYOFYEAR(dateOfBirth)
FROM Youngest)
SELECT acctno, name, address, status,
YEAR(CURRENT_DATE) - INT(dob7 / 1000)
- CASE WHEN DAYOFYEAR(CURRENT_DATE) < MOD(dbo7, 1000)
THEN 1
ELSE 0 END as Age
FROM myData.cdMast cdMast
JOIN Converted
ON Converted.formatted >= dob7
LEFT JOIN myData.cfMast cfMast
ON cdMast.cifno = cfMast.cifno
WHERE status <> 'R'
Please note that it will consider people born on a leap day to have had their birthday on March 1st (due to DAYOFYEAR()).
From the DATE scalar function documentation:
A string with an actual length of 7 that represents a valid date in the form yyyynnn, where yyyy are digits denoting a year, and nnn are digits between 001 and 366 denoting a day of that year.
Reformat the date with:
DATE(SUBSTR(DIGITS(DOB7),4,4) || SUBSTR(DIGITS(DOB7),1,3))
To select 70.5 or older by the end of the current year:
YEAR(CURRENT_DATE) - YEAR(DATE(SUBSTR(DIGITS(DOB7),4,4) || SUBSTR(DIGITS(DOB7),1,3))) = 70
AND MONTH(DATE(SUBSTR(DIGITS(DOB7),4,4) || SUBSTR(DIGITS(DOB7),1,3))) >= 6
OR YEAR(CURRENT_DATE) - YEAR(DATE(SUBSTR(DIGITS(DOB7),4,4) || SUBSTR(DIGITS(DOB7),1,3))) > 70
The error message is saying that the contents of DOB7 cannot be converted to a date. Does the value of DOB7 match one of the valid formats? Note that many require quotation marks. http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/db2/rbafzscadate.htm
Try this instead:
(year(curdate()) - mod(dob7, 10000)) >= 70
This is using modular arithmetic to extract the year, rather than trying to convert it to a date.
By the way, storing the date this way seems very awkward. Databases have built-in support for dates and times, so it is usually better to store them in the native format.
If you date of birth is really yyyymmm, then the following should work for years:
(year(curdate()) - cast(dob7/1000 as int)) >= 70
For the half year:
(year(curdate()) - cast(dob7/1000 as int))+(1-mod(dob7,1000)/365.0) >= 70.5