Oracle sql - date subtraction within a function - sql

(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..

Related

Calculate age from date of birth with formatting

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;

PL/SQL function with date parameter returns wrong output

In order to make my main code more readable, I have wrapped an operation to convert a date to epoch format in a function. However, the function returns an incorrect value. What am I doing wrong?
What I get
Function
create or replace function date_to_epoch(
date_in in date)
return number
is
epoch_out number;
begin
epoch_out := round((to_date(date_in, 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60);
return epoch_out;
end;
I call the function with:
declare
date_out number;
begin
select date_to_epoch(to_date('2020-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) into date_out from dual;
DBMS_OUTPUT.PUT_LINE('Output:' || date_out);
end;
returns: -62134128000
What I expect
I get the expected output when I run the same operation in a simple SQL statement:
select round((to_date('2020-01-01', 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60) from dual;
returns: 1577836800
Your input parameter is a date already, so you should not use to_date() on it. Bad things happen when you do that.
I would write your code as:
create or replace function date_to_epoch(date_in in date)
return number
is
epoch_out number;
begin
epoch_out := round((date_in - date '1970-01-01')*24*60*60);
return epoch_out;
end;
Note that you don't actually need an intermediate variable to store the return value of the function. You can directly do:
create or replace function date_to_epoch(date_in in date)
return number
is
begin
return round((date_in - date '1970-01-01')*24*60*60);
end;
In this demo on DB Fiddle, the code yields:
Output:1577836800
What am I doing wrong?
The TO_DATE function has the signature:
TO_DATE( date_string, format_model, nls_param )
Where all the arguments are expected to have a string data-type.
You are not passing a string as the first argument; you are passing a DATE data type. Oracle will try to be helpful and convert the DATE you are passing to a string by implicitly calling TO_CHAR using the current session's NLS date format session parameter.
So, your function is effectively:
create or replace function date_to_epoch(
date_in in date
) return number
is
epoch_out number;
begin
epoch_out := round(
( to_date(
TO_CHAR(
date_in,
( SELECT value
FROM NLS_SESSION_PARAMETERS
WHERE parameter = 'NLS_DATE_FORMAT' )
),
'yyyy-mm-dd hh24:mi:ss'
)
-
to_date(
'1970-01-01',
'yyyy-mm-dd'
)
)*24*60*60
);
return epoch_out;
end;
To fix it, you need to just use the date input as a date rather than trying to convert something that is already a date to a date:
CREATE FUNCTION date_to_epoch(
date_in IN DATE
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
RETURN ROUND( ( date_in - DATE '1970-01-01' )*24*60*60 );
END date_to_epoch;
However, you also need to make sure that your dates are in the UTC time zone and if they aren't convert them to UTC before calculating the epoch:
CREATE FUNCTION date_to_epoch(
date_in IN DATE,
timezone IN VARCHAR2 DEFAULT 'UTC'
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
RETURN ROUND(
( CAST(
FROM_TZ( date_in, timezone ) -- Convert to a timestamp in the correct TZ
AT TIME ZONE 'UTC' -- Change to the UTC TZ
AS DATE -- Cast back to a date
)
- DATE '1970-01-01'
)*24*60*60
);
END date_to_epoch;
/
So:
SELECT date_to_epoch( DATE '1970-01-01' ) AS utc_epoch,
date_to_epoch( DATE '1970-01-01', 'PST' ) AS pst_epoch
FROM DUAL;
outputs:
UTC_EPOCH | PST_EPOCH
--------: | --------:
0 | 28800
db<>fiddle here

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.

How to average time intervals?

In Oracle 10g I have a table that holds timestamps showing how long certain operations took. It has two timestamp fields: starttime and endtime. I want to find averages of the durations given by these timestamps. I try:
select avg(endtime-starttime) from timings;
But get:
SQL Error: ORA-00932: inconsistent
datatypes: expected NUMBER got
INTERVAL DAY TO SECOND
This works:
select
avg(extract( second from endtime - starttime) +
extract ( minute from endtime - starttime) * 60 +
extract ( hour from endtime - starttime) * 3600) from timings;
But is really slow.
Any better way to turn intervals into numbers of seconds, or some other way do this?
EDIT:
What was really slowing this down was the fact that I had some endtime's before the starttime's. For some reason that made this calculation incredibly slow. My underlying problem was solved by eliminating them from the query set. I also just defined a function to do this conversion easier:
FUNCTION fn_interval_to_sec ( i IN INTERVAL DAY TO SECOND )
RETURN NUMBER
IS
numSecs NUMBER;
BEGIN
numSecs := ((extract(day from i) * 24
+ extract(hour from i) )*60
+ extract(minute from i) )*60
+ extract(second from i);
RETURN numSecs;
END;
There is a shorter, faster and nicer way to get DATETIME difference in seconds in Oracle than that hairy formula with multiple extracts.
Just try this to get response time in seconds:
(sysdate + (endtime - starttime)*24*60*60 - sysdate)
It also preserves fractional part of seconds when subtracting TIMESTAMPs.
See http://kennethxu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html for some details.
Note that custom pl/sql functions have significant performace overhead that may be not suitable for heavy queries.
If your endtime and starttime aren't within a second of eachother, you can cast your timestamps as dates and do date arithmetic:
select avg(cast(endtime as date)-cast(starttime as date))*24*60*60
from timings;
It doesn't look like there is any function to do an explicit conversion of INTERVAL DAY TO SECOND to NUMBER in Oracle. See the table at the end of this document which implies there is no such conversion.
Other sources seem to indicate that the method you're using is the only way to get a number from the INTERVAL DAY TO SECOND datatype.
The only other thing you could try in this particular case would be to convert to number before subtracting them, but since that'll do twice as many extractions, it will likely be even slower:
select
avg(
(extract( second from endtime) +
extract ( minute from endtime) * 60 +
extract ( hour from endtime ) * 3600) -
(extract( second from starttime) +
extract ( minute from starttime) * 60 +
extract ( hour from starttime ) * 3600)
) from timings;
SQL Fiddle
Oracle 11g R2 Schema Setup:
Create a type to use when performing a custom aggregation:
CREATE TYPE IntervalAverageType AS OBJECT(
total INTERVAL DAY(9) TO SECOND(9),
ct INTEGER,
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT IntervalAverageType
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT IntervalAverageType,
value IN INTERVAL DAY TO SECOND
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(
self IN OUT IntervalAverageType,
returnValue OUT INTERVAL DAY TO SECOND,
flags IN NUMBER
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT IntervalAverageType,
ctx IN OUT IntervalAverageType
) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY IntervalAverageType
IS
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT IntervalAverageType
) RETURN NUMBER
IS
BEGIN
ctx := IntervalAverageType( INTERVAL '0' DAY, 0 );
RETURN ODCIConst.SUCCESS;
END;
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT IntervalAverageType,
value IN INTERVAL DAY TO SECOND
) RETURN NUMBER
IS
BEGIN
IF value IS NOT NULL THEN
self.total := self.total + value;
self.ct := self.ct + 1;
END IF;
RETURN ODCIConst.SUCCESS;
END;
MEMBER FUNCTION ODCIAggregateTerminate(
self IN OUT IntervalAverageType,
returnValue OUT INTERVAL DAY TO SECOND,
flags IN NUMBER
) RETURN NUMBER
IS
BEGIN
IF self.ct = 0 THEN
returnValue := NULL;
ELSE
returnValue := self.total / self.ct;
END IF;
RETURN ODCIConst.SUCCESS;
END;
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT IntervalAverageType,
ctx IN OUT IntervalAverageType
) RETURN NUMBER
IS
BEGIN
self.total := self.total + ctx.total;
self.ct := self.ct + ctx.ct;
RETURN ODCIConst.SUCCESS;
END;
END;
/
Then you can create a custom aggregation function:
CREATE FUNCTION AVERAGE( difference INTERVAL DAY TO SECOND )
RETURN INTERVAL DAY TO SECOND
PARALLEL_ENABLE AGGREGATE USING IntervalAverageType;
/
Query 1:
WITH INTERVALS( diff ) AS (
SELECT INTERVAL '0' DAY FROM DUAL UNION ALL
SELECT INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT INTERVAL '-1' DAY FROM DUAL UNION ALL
SELECT INTERVAL '8' HOUR FROM DUAL UNION ALL
SELECT NULL FROM DUAL
)
SELECT AVERAGE( diff ) FROM intervals
Results:
| AVERAGE(DIFF) |
|---------------|
| 0 2:0:0.0 |
Well, this is a really quick and dirty method, but what about storing the seconds difference in a separate column (you'll need to use a trigger or manually update this if the record changes) and averaging over that column?
Unfortunately Oracle does not support most functions with intervals. There are a number of workarounds for this, but they all have some kind of drawback (and notably, none are ANSI-SQL compliant).
The best answer (as #justsalt later discovered) is to write a custom function to convert the intervals into numbers, average the numbers, then (optionally) convert back to intervals. Oracle 12.1 and later support doing this using a WITH block to declare a function:
with
function fn_interval_to_sec(i in dsinterval_unconstrained)
return number is
begin
return ((extract(day from i) * 24
+ extract(hour from i) )*60
+ extract(minute from i) )*60
+ extract(second from i);
end;
select numtodsinterval(avg(fn_interval_to_sec(endtime-starttime)), 'SECOND')
from timings;
If you are on 11.2 or earlier, or if you prefer not to include functions in your SQL statements, you can declare it as a stored function:
create or replace function fn_interval_to_sec(i in dsinterval_unconstrained)
return number is
begin
return ((extract(day from i) * 24
+ extract(hour from i) )*60
+ extract(minute from i) )*60
+ extract(second from i);
end;
You can then use it in SQL as expected:
select numtodsinterval(avg(fn_interval_to_sec(endtime-starttime)), 'SECOND')
from timings;
Using dsinterval_unconstrained
Using the PL/SQL type alias dsinterval_unconstrained for the function parameter ensures you have maximum precision/scale; INTERVAL DAY TO SECOND defaults DAY precision to 2 digits (meaning anything at or over ±100 days is an overflow and throws an exception) and SECOND scale to 6 digits.
Additionally, Oracle 12.1 will raise a PL/SQL error if you try to specify any precision/scale in your parameter:
with
function fn_interval_to_sec(i in interval day(9) to second(9))
return number is
...
ORA-06553: PLS-103: Encountered the symbol "(" when expecting one of the following: to
Alternatives
Custom aggregate function
Oracle supports custom aggregate functions written in PL/SQL, which would allow you to make minimal changes to the statement:
select ds_avg(endtime-starttime) from timings;
However, this approach has several major drawbacks:
You have to create the PL/SQL aggregate objects in your database, which may not be desired or allowed;
You cannot name it avg, as Oracle will always use the builtin avg function rather than your own. (Technically you can, but then you have to qualify it with schema, which defeats the purpose.)
As #vadzim noted, aggregate PL/SQL functions have significant performance overhead.
Date arithmetic
If your values are not significantly far apart, #vadzim's approach works as well:
select avg((sysdate + (endtime-starttime)*24*60*60*1000000 - sysdate)/1000000.0)
from timings;
Be aware, though, that if the interval is too great, the (endtime-starttime)*24*60*60*1000000 expression will overflow and throw ORA-01873: the leading precision of the interval is too small. At this precision (1μs) the difference cannot be greater than or equal to 00:16:40 in magnitude, so it is safe for small intervals, but not all.
Finally, if you are comfortable losing all subsecond precision, you can cast the TIMESTAMP columns to DATE; subtracting a DATE from a DATE will return the number of days with second precision (credit to #jimmyorr):
select avg(cast(endtime as date)-cast(starttime as date))*24*60*60
from timings;

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)