Calculate age from date of birth with formatting - sql

I would like to calculate the age from the date of birth stored as a string using the format in the comments in the code. Here's my code:
--pnr values
--'19490321-7000'
--'19540201-7000'
--'19650823-7000'
declare
v_now date :=to_date(sysdate, 'YYYY-MM-DD');
v_dob date;
v_age number;
cursor c_carowners is select fnamn, enamn, pnr
from carowners;
begin
for v_rec in c_carowners loop
v_dob:= to_date(substr(v_rec.pnr,1,8), 'YYYY-MM-DD');
v_age := (months_between(v_now, v_dob))/12;
dbms_output.put_line('Age is: '||v_age);
end loop;
end;
The result I get is the following:
Statement processed.
Age is: -1935.252688172043010752688172043010752692
Age is: -1940.115591397849462365591397849462365592
Age is: -1951.674731182795698924731182795698924733
I guess there's something wrong with the year but I can't figure it out!

The error is in this line:
v_now date :=to_date(sysdate, 'YYYY-MM-DD');
You should not call to_date on sysdate, because sysdate already has the data type date.
You should just do:
v_now date := sysdate;
Or, even leave out that variable, and use sysdate directly in your calculation.
I would also change the other date format to YYYYMMDD, since your table data has no hyphens.
Note that you can do this without PL/SQL, with a query:
select pnr,
months_between(sysdate, to_date(substr(pnr,1,8), 'YYYYMMDD'))/12
from carowners;

Related

Checking if date falls in a specified month in Oracle

procedure Charge(p_inputdate N VARCHAR2,//Date which accepts only MMYYYY format
inout_Cur OUT result_cur) IS
I need to validate the effective_date which is in date format (mm/dd/yyyy hh:mm:ss)
For example: The user enters 112013 as input and the effective date to check is 11/12/2013 09:13:22
Is it possible to find whether the effective date falls within the specified month?
can you please comment on this..
I tried below way:
V_INPUTDATE := to_char(TO_DATE(IN_INPUTDATE, 'MMYYYY','mmyyyy'));
v_effectiveDate := substr(V_INPUTDATE,1,2)+'/'+'01'+'/'+substr(V_INPUTDATE,3,4);
01 is nothing but month always start with 01(dd)
Is this rightway ..??
If date happens to be a VARCHAR2 you need to first convert it to DATE:
effective_date DATE; -- Put this line in the declare section
effective_date := to_date('11/12/2013 09:13:22', 'MM/DD/YYYY HH24:MI:SS');
When you have the date, the rest is easy:
IF p_inputdate = to_char(effective_date, 'MMYYYY') THEN
-- Date is ok.
ELSE
-- Date is not ok.
END IF;

Identify date format in PLSQL

I'm trying to write a plsql stored procedure which identifies any date format and converts it into single datetime format 'mm/dd/yyyy hh:mi:ss'. How do I do it. I tried using case statment but there are so many date combinations that its not possible to write case statment for all of them.
For ex: 27-Oct-1967, October 27 1967 11:15:45, 1967-OCT-27, etc.
How do I convert all these to single format.
Thank you.
Simply to_char() will do,
select to_char(yourDateField,'mm/dd/yyyy hh:mi:ss') from dual;
Maybe this can help you:
CREATE TABLE temp_date
AS
SELECT '1967-OCT-27' some_date
FROM dual
UNION
SELECT '27-Oct-1967' FROM dual
UNION
SELECT 'October 27 1967 11:15:45' FROM dual
/
Declare
CURSOR i_cur IS
Select some_date
From temp_date;
--
v_date1 Varchar2(30);
v_date2 Varchar2(30);
v_date3 Varchar2(30);
v_char Varchar2(30);
v_cnt Number:= 0;
Begin
For i_rec IN i_cur
Loop
v_cnt:= v_cnt + 1;
Begin
v_date1:= to_char(to_date(i_rec.some_date), 'MM/DD/YYYY hh24:mi:ss');
dbms_output.put_line(v_cnt||'.'||chr(9)||v_date1);
EXCEPTION
When Others Then
Begin
v_date2:= to_char(to_date(i_rec.some_date, 'MM/DD/YYYY hh24:mi:ss'), 'MM/DD/YYYY hh24:mi:ss');
dbms_output.put_line(v_cnt||'.'||chr(9)||v_date2);
EXCEPTION
When Others Then
Begin
v_date3:= to_char(to_date(i_rec.some_date, 'YYYY-MON-DD'), 'MM/DD/YYYY hh24:mi:ss');
dbms_output.put_line(v_cnt||'.'||chr(9)||v_date3);
EXCEPTION
When Others Then
-- Insert into Exceptions table (or any temp table) then convert... --
v_char:= i_rec.some_date;
dbms_output.put_line(v_cnt||'. '||chr(9)||i_rec.some_date||' : '||v_char);
End;
End;
End;
End Loop;
End;
/
1. 10/27/1967 00:00:00
2. 10/27/1967 00:00:00
3. 10/27/1967 11:15:45
The most desired way of would be to formatting date into the format that you need. Then do whatever calculations that you require on it.
Otherwise you will have to write ridiculously large number of select cases to define the format. Not to say that it doesn't make sense since dates can come in many different formats....as Mat mentioned. And further Date is a component that can be influenced by your system.
You may try the following:
Convert date input into the desired format using To_Date() given you may not even know if this input comes as a String or a real date. So you may need some validations to make sure it's a proper date.
SELECT TO_DATE(mydate,'mm/dd/yyyy hh:mi:ss') FROM Dual;

Select doesn't return expected result with Date type in Oracle DB

Table my_table is:
CREATE TABLE MY_TABLE(
ID NUMBER NOT NULL,
MY_DATE DATE NOT NULL);
Typing the following query:
select sysdate from dual
the result is:
10-MAG-12 21:22:32
note that mag = may.
Now, if I type this query:
select *
from my_table
where my_date <= sysdate
the result is:
9918 10-MAG-12 20:00:00
9915 10-MAG-12 21:00:00
9952 10-MAG-12 22:00:00
9951 10-MAG-12 23:00:00
Note that in my_table I have only these 4 records. Why I see all the records and not the first and second record only? Thanks.
I use Oracle SQL Developer.
Edit: please note that when I insert a record with PL/SQL I type something like:
nCount NUMBER;
myDate DATE;
stringDate VARCHAR2(255);
BEGIN
nCount := 0;
stringDate := substr(to_char(trunc(sysdate)),0,9);
myDate := to_date(stringDate || ' 20:00:00','dd-mm-yyyy hh24:mi:ss');
for t in (a cursor) loop
insert into MY_TABLE(ID,MY_DATE)
values (9918,myDate+(nCount/1440));
nCount := nCount + 60;
end loop;
END;
I suspect that the data being stored in your table does not have a year of 2012. It probably has a year of 0012 (two thousand years ago).
What do you see when you run the query
SELECT id, to_char( my_date, 'dd-mm-yyyy hh24:mi:ss' )
FROM my_table
I expect that the year will be 0012 rather than 2012. The reason for that is that the code you're using to insert the data is incorrectly converting a date to a string without using an explicit format mask then converts the string back to a date using an explicit format mask that happens not to match the session's NLS_DATE_FORMAT. In general, if you ever find yourself converting a date to a string and back to a date, you're probably doing something wrong. If you change your code to simply do date manipulation, it will be more efficient, more robust, and less error-prone.
DECLARE
nCount NUMBER;
myDate DATE;
BEGIN
nCount := 0;
myDate := trunc(sysdate) + interval '20' hour;
for t in (a cursor) loop
insert into MY_TABLE(ID,MY_DATE)
values (9918,myDate+(nCount/1440));
nCount := nCount + 60;
end loop;
END;
Walking through why the original code goes wrong
stringDate := substr(to_char(trunc(sysdate)),0,9);
This takes SYSDATE and truncates it to midnight on the current day. So far, so good. Then, it calls TO_CHAR without an explicit format mask. This causes Oracle to use the session's NLS_DATE_FORMAT, meaning that different users with different settings will get different results. If your session's NLS_DATE_FORMAT happens to be 'dd-mon-rr hh24:mi:ss', which I'm guessing based on the query results you posted, that will mean that the string has a 2-digit year. Your SUBSTR appears to assume that the output has just a two-digit year (if you have a different NLS_DATE_FORMAT, your SUBSTR will generate different bugs such as potentially cutting off the 12 from a year of 2012 leaving a year of just 20).
myDate := to_date(stringDate || ' 20:00:00','dd-mm-yyyy hh24:mi:ss');
Assuming stringDate is something like 10-MAG-12, this next line generates a string 10-MAG-12 20:00:00 and then tries to convert it to a date using the format mask dd-mm-yyyy hh24:mi:ss. This assumes that the string has a 4-digit year so when it only finds 2-digits, it assumes that you meant the year 12, not the year 2012.

Oracle sql - date subtraction within a function

(moved from previous post) - sorry if this is seen as a repeat!
Hi everyone,
Just having some issues with date calculations and subtracting from a date within a function.
I am confused with the data types I should be using as I am having to convert from date to_char for example. I'm not sure whether to have the return type as varchar2 or date?
Here is the function that receives a car_id, looks in the table for that car, pulls out the cars arrive date, stores it in a date variable
does the same with the departure date.
Then converts both dates to_char, does the subtraction and returns it in a varchar2.
When I call the function it will inputting the car_id as a parameter,
then storing the returned result in a varchar variable, for a dbms output.
eg.
v_result := get_duration('0001')
here is the function:
DROP FUNCTION get_duration;
CREATE FUNCTION get_duration (p_car_id number)
RETURN varchar2 is
v_arrive date;
v_depart date
v_duration varchar2(25); --not too sure about this variable choice
begin
select arrival, departure
into v_arrive, v_depart
from car_info
where car_id = p_car_id;
v_duration := to_char(v_depart, 'dd-mon-yyyy hh24:mi:ss') - to_char(v_arrive, 'dd-mon-yyyy hh24:mi:ss')
return v_duration;
END;
/
As you can see, I am trying to put the start and end times from the table in to the variables, then minus the end date from the start date.
The function compiles, but with a warning, and when I got to call the function, the error: get_duration is invalid
Any input on this would be greatly received,
Sorry if the post is relatively big!
regards,
Darren
Trivial problems are that you're missing a ; when you define v_depart, and at the end of the line you assign the value to v_duration; and you're mixing up your variable names. (You're also inconsistent about the type of car_info.id; you've created it as a varchar when it probably ought to be a number, but that's more of a comment on your previous question).
The main problem is that you can't perform a minus on two strings, as that doesn't really mean anything. You need to do the manipulation of the original dates, and then figure out how you want to return the result to the caller.
Subtracting one date from another gives a number value, which is the number of days; partial days are fractions, so 0.25 is 6 hours. With the dates from your previous quesiton, this query:
select arrival, departure, departure - arrival as duration
from car_info
where car_id = 1;
... shows duration of 2.125, which is 2 days and 3 hours.
This isn't the best way to do this, but to show you the process of what's going on I'll use that duration number and convert it into a string in quite a long-winded way:
CREATE OR REPLACE FUNCTION get_duration (p_car_id number)
RETURN varchar2 is
v_arrive date;
v_depart date;
v_duration number;
v_days number;
v_hours number;
v_minutes number;
v_seconds number;
BEGIN
select arrival, departure, departure - arrival
into v_arrive, v_depart, v_duration
from car_info
where car_id = p_car_id;
-- Days is the whole-number part, which you can get with trunc
v_days := trunc(v_duration);
-- Hours, minutes and seconds are extracted from the remainder
v_hours := trunc(24 * (v_duration - v_days));
v_minutes := trunc(60 * (v_duration - v_days - (v_hours/24)));
v_seconds := trunc(60 * (v_duration - v_days - (v_hours/24)
- (v_minutes/(24*60))));
return v_days || ' days '
|| to_char(v_hours, '00') || ' hours '
|| to_char(v_minutes, '00') || ' minutes '
|| to_char(v_seconds, '00') || ' seconds';
END;
/
Function created.
show errors
No errors.
select get_duration(1) from dual;
GET_DURATION(1)
--------------------------------------------------------------------------------
2 days 03 hours 00 minutes 00 seconds
You can play with the number format masks etc. to get the output you want.
DATE arithmetic works on DATEs not on VARCHAR2s:
CREATE FUNCTION get_duration (p_car_id number)
RETURN varchar2 is
v_arrive date;
v_depart date
v_duration number; --<<<
begin
select arrival, departure
into v_arrive, v_depart
from car_info
where car_id = p_car_id;
v_duration := v_arrive - v_depart;
return v_duration;
END;
That gives a result in days.
You cannot subtract dates once you cast them into varchar2 (to_char function does that!). Instead use v_duration := v_stop - v_start; which will give you the result 'v_duration' in days. Then you can convert it to hours, minutes, etc..

Oracle equivalent to SQL Server/Sybase DateDiff

We are now using NHibernate to connect to different database base on where our software is installed. So I am porting many SQL Procedures to Oracle.
SQL Server has a nice function called DateDiff which takes a date part, startdate and enddate.
Date parts examples are day, week, month, year, etc. . .
What is the Oracle equivalent?
I have not found one do I have to create my own version of it?
(update by Mark Harrison) there are several nice answers that explain Oracle date arithmetic. If you need an Oracle datediff() see Einstein's answer. (I need this to keep spme SQL scripts compatible between Sybase and Oracle.) Note that this question applies equally to Sybase.
I stole most of this from an old tom article a few years ago, fixed some bugs from the article and cleaned it up. The demarcation lines for datediff are calculated differently between oracle and MSSQL so you have to be careful with some examples floating around out there that don't properly account for MSSQL/Sybase style boundaries which do not provide fractional results.
With the following you should be able to use MSSQL syntax and get the same results as MSSQL such as SELECT DATEDIFF(dd,getdate(),DATEADD(dd,5,getdate())) FROM DUAL;
I claim only that it works - not that its effecient or the best way to do it. I'm not an Oracle person :) And you might want to think twice about using my function macros to workaround needing quotes around dd,mm,hh,mi..etc.
(update by Mark Harrison) added dy function as alias for dd.
CREATE OR REPLACE FUNCTION GetDate
RETURN date IS today date;
BEGIN
RETURN(sysdate);
END;
/
CREATE OR REPLACE FUNCTION mm RETURN VARCHAR2 IS BEGIN RETURN('mm'); END;
/
CREATE OR REPLACE FUNCTION yy RETURN VARCHAR2 IS BEGIN RETURN('yyyy'); END;
/
CREATE OR REPLACE FUNCTION dd RETURN VARCHAR2 IS BEGIN RETURN('dd'); END;
/
CREATE OR REPLACE FUNCTION dy RETURN VARCHAR2 IS BEGIN RETURN('dd'); END;
/
CREATE OR REPLACE FUNCTION hh RETURN VARCHAR2 IS BEGIN RETURN('hh'); END;
/
CREATE OR REPLACE FUNCTION mi RETURN VARCHAR2 IS BEGIN RETURN('mi'); END;
/
CREATE OR REPLACE FUNCTION ss RETURN VARCHAR2 IS BEGIN RETURN('ss'); END;
/
CREATE OR REPLACE Function DateAdd(date_type IN varchar2, offset IN integer, date_in IN date )
RETURN date IS date_returned date;
BEGIN
date_returned := CASE date_type
WHEN 'mm' THEN add_months(date_in,TRUNC(offset))
WHEN 'yyyy' THEN add_months(date_in,TRUNC(offset) * 12)
WHEN 'dd' THEN date_in + TRUNC(offset)
WHEN 'hh' THEN date_in + (TRUNC(offset) / 24)
WHEN 'mi' THEN date_in + (TRUNC(offset) /24/60)
WHEN 'ss' THEN date_in + (TRUNC(offset) /24/60/60)
END;
RETURN(date_returned);
END;
/
CREATE OR REPLACE Function DateDiff( return_type IN varchar2, date_1 IN date, date_2 IN date)
RETURN integer IS number_return integer;
BEGIN
number_return := CASE return_type
WHEN 'mm' THEN ROUND(MONTHS_BETWEEN(TRUNC(date_2,'MM'),TRUNC(date_1, 'MM')))
WHEN 'yyyy' THEN ROUND(MONTHS_BETWEEN(TRUNC(date_2,'YYYY'), TRUNC(date_1, 'YYYY')))/12
WHEN 'dd' THEN ROUND((TRUNC(date_2,'DD') - TRUNC(date_1, 'DD')))
WHEN 'hh' THEN (TRUNC(date_2,'HH') - TRUNC(date_1,'HH')) * 24
WHEN 'mi' THEN (TRUNC(date_2,'MI') - TRUNC(date_1,'MI')) * 24 * 60
WHEN 'ss' THEN (date_2 - date_1) * 24 * 60 * 60
END;
RETURN(number_return);
END;
/
JohnLavoie - you don't need that. DATE in Oracle is actually a date and time data type. The only difference between DATE and TIMESTAMP is that DATE resolves down to the second but TIMESTAMP resolves down to the micro second. Therefore the Ask Tom article is perfectly valid for TIMESTAMP columns as well.
Tom's article is very old. It only discusses the DATE type. If you use TIMESTAMP types then date arithmetic is built into PL/SQL.
http://www.akadia.com/services/ora_date_time.html
DECLARE
ts_a timestamp;
ts_b timestamp;
diff interval day to second;
BEGIN
ts_a := systimestamp;
ts_b := systimestamp-1/24;
diff := ts_a - ts_b;
dbms_output.put_line(diff);
END;
+00 01:00:00.462000
or
DECLARE
ts_b timestamp;
ts_a timestamp;
date_part interval day to second;
BEGIN
ts_a := systimestamp;
date_part := to_dsinterval('0 01:23:45.678');
ts_b := ts_a + date_part;
dbms_output.put_line(ts_b);
END;
04-SEP-08 05.00.38.108000 PM
YOU Could write a function in oracle for this
function datediff( p_what in varchar2, p_d1 in date, p_d2 in date) return number as l_result number;
BEGIN
select (p_d2-p_d1) *
decode( upper(p_what), 'SS', 24*60*60, 'MI', 24*60, 'HH', 24, NULL )
into l_result from dual;
return l_result;
END;
and use it like :
DATEDIFF('YYYY-MM-DD', SYSTIMESTAMP, SYSTIMESTAMP)