Oracle PLSQL get difference between 2 datetime fields ignoring days - oracle

I would like to find the simplest way of calculating the difference in hours between 2 dates from Oracle datetime fields whilst ignoring the days, months & years portion. For example:
Datetime 1 (DATE variable) = 10/05/2017 16:00:00
Datetime 2 (DATE variable) = 15/05/2017 19:34:23
Required result (NUMBER output) = 3.576 hours
This is formula will be used in a PLSQL procedure, the output needs to be a number as above. I would imagine some combination of TO_DATE & TRUNC might work. Any help would be most appriciated and apologies if this is a duplicate question.

Use the sssss date mask to get just the time element as the number of seconds since midnight. Then it's just a matter of simple arithmentic:
select (to_number(to_char(datetime2, 'sssss'))
- to_number(to_char(datetime1, 'sssss')) / 3600 as diff_hours
from dual;
PL/SQL version is the same....
declare
Datetime1 DATE := to_date('10/05/2017 16:00:00', 'dd/mm/yyyy hh24:mi:ss');
Datetime2 DATE := to_date('15/05/2017 19:34:23', 'dd/mm/yyyy hh24:mi:ss');
hours_diff number;
begin
hours_diff := (to_number(to_char(datetime2, 'sssss'))
- to_number(to_char(datetime1, 'sssss'))) / 3600 ;
dbms_output.put_line(hours_diff);
end;

Related

Oracle get all records between current day inserted with timestamp

Unable to retrieve records properly with the formatted date values, need select query with right date format to get all records inserted per day
I have a date string like this in my script -  
dateString :='26-MAR-20 05.00.00.00000000 AM';
I want to add 0.313 minutes to this date value. And also I want to
increment it to the next day something like this- '27-MAR-20
05.00.00.00000000 AM'; ​
I tried this 
dateString :='26-MAR-20 05.00.00.00000000 AM';
dateString :=to_char(dateField,'DD-MON-YYYY HH24:MI:SS.FF');
dateField := to_timestamp(dateString, 'DD/MON/YYYY HH24:MI:SS.FF') + 0.313 * INTERVAL '1' MINUTE;
​
I can see the output and inserted these values into the DB but
unable to retrieve records properly with these formatted date values..
I suspect it might be to do with the timestamp fields
When I run this query -
select *from rptallexceptions where exceptiontime between '27-MAR-2020 04.00.00.000000000 AM' and '28-MAR-2020 03.59.00.000000000 AM' order by exceptiontime desc;
​
--- this one gives 3833 records but expected is 4600
it shows only the records on 27th march., records inserted with timestamp containing 28th march are not retrieved..
select *from rptallexceptions where exceptiontime between '28-MAR-2020 04.00.00.000000000 AM' and '29-MAR-2020 03.59.00.000000000 AM' order by exceptiontime desc;
--- this one returns '0' rows
Excerpt from the script:
cnt :=cnt +1;
dateString :=to_char(dateField,'DD-MON-YYYY HH24:MI:SS.FF');
-- add time difference for each exception.
dateField := to_timestamp(dateString, 'DD/MON/YYYY HH24:MI:SS.FF') + 0.313 * INTERVAL '1' MINUTE;
-- after n*4600 exceptions,update date to next date.
IF REMAINDER(cnt,exceptionsPerDay) = 0 THEN
dateField := to_timestamp(dateField + 1,'DD/MON/YYYY HH24:MI:SS.FF');
END IF;
The way I see it, your problem is that you're comparing timestamps to strings.
Sample table:
SQL> create table test as
2 select to_timestamp('27.03.2020 04:00:00:000000', 'dd.mm.yyyy hh24:mi:ss:ff6') datum, 'A' name from dual
3 union all
4 select to_timestamp('28.03.2020 15:23:00:000000', 'dd.mm.yyyy hh24:mi:ss:ff6') datum, 'A' name from dual;
Table created.
Query should use timestamp, not string:
SQL> select *
2 from test
3 where datum between to_timestamp('25.03.2020 12:00:00:000000am', 'dd.mm.yyyy hh:mi:ss:ff6am') --> timestamp
4 and to_timestamp('28.03.2020 12:00:00:000000am', 'dd.mm.yyyy hh:mi:ss:ff6am'); --> timestamp
DATUM N
--------------------------------------------------------------------------- -
27.03.20 04:00:00,000000000 A
SQL>
You were kind of "lucky" (though, one might call it bad luck as your query didn't fail, but it produced wrong results, according to number of rows being returned) for not getting an error as my database raises (due to different NLS settings):
SQL> select *
2 from test
3 where datum between '25.03.2020 12:00:00:000000am' --> string
4 and '28.03.2020 12:00:00:000000am'; --> string
where datum between '25.03.2020 12:00:00:000000am'
*
ERROR at line 3:
ORA-01830: date format picture ends before converting entire input string
SQL>
Strings are handled differently than dates (or timestamps), or numbers. Maybe you'll see the difference clearly in this example:
SQL> with test (col) as
2 (select '1' from dual union all
3 select '9' from dual union all
4 select '20' from dual
5 )
6 select *
7 from test
8 where col < '9';
CO
--
1
20
SQL>
20 < 9? Yes, if those are strings. The same might have happened to you. Try with proper datatype.
When you do:
dateString :=to_char(dateField,'DD-MON-YYYY HH24:MI:SS.FF');
you are converting whatever is the initial value of dateField to a string; but it looks like that has has not been set yet, so you end up with an empty string (which is the same as null). Your original value of dateString is never being used.
If you skip that and just do:
dateString :='26-MAR-20 05.00.00.00000000 AM';
dateField := to_timestamp(dateString, 'DD/MON/YYYY HH24:MI:SS.FF') + 0.313 * INTERVAL '1' MINUTE;
​then you are converting the 2-digit year 20 with a YYYY format element, which will turn it into year 0020, not 2020; but you also don't have AM in the format model, so it will get ORA-01830 anyway, and have HH24 instead of HH. I think your to_char() is attempting to 'correct' the string format, but that's not a great approach.
If you can't control the starting string format then the model has to match that:
dateString :='26-MAR-20 05.00.00.00000000 AM';
dateField := to_timestamp(dateString, 'DD-MON-RR HH:MI:SS.FF AM') + 0.313 * INTERVAL '1' MINUTE;
which gives dateField a value of 2020-03-26 05:00:18.780000.
Later on you do:
dateField := to_timestamp(dateField + 1,'DD/MON/YYYY HH24:MI:SS.FF');
which is also doing an implicit conversion of dateField + 1 - which is converted to a date, incidentally as timestamp + number is a date, not a timestamp - to a string; which will use your NLS_DATE_FORMAT. Presumably that is something like 'DD-MON-YYYY'.
So stepping through that:
dateField + 1 => date '2020-03-27 05:00:18' (losing fractional seconds)
implicit to_char(dateField + 1) => string '27-Mar-2020' (losing time)
to_timestamp(to_char(dateField + 1), '...') => timestamp '2020-03-27 00:00:00.000000' (with time at midnight).
So your between is, at best going to find records between 2020-03-26 05:00:18.780000 and 2020-03-27 00:00:00.0000000 - so it won't pick up any records later than midnight on the 27th.
Don't convert to and from strings when you don't need to; leave data in its native data type (timestamp in this case) and use direct manipulation with intervals.
Using between isn't ideal anyway, because it is inclusive; it would be better to end up with:
exceptiontime >= timestamp '2020-03-26 05:00:18.780000'
and exceptionTime < timestamp '2020-03-27 05:00:18.780000'

create pl sql trigger

I am trying to write a Trigger for before insert to validate a case date.
The case date should be between 5 years before and 7 years after current date.
For example, in 2018 the case date should be from year 2013 to 2025. If the date is out of range the trigger should stop inserting data.
CREATE OR REPLACE TRIGGER ch
BEFORE INSERT
on CASE
FOR EACH ROW
DECLARE
CASN number;
BEGIN
SELECT COUNT(*)
INTO CASN
FROM CASE
WHERE :new.STARTDATE > SYSDATE;
IF (CASN > 0) THEN
RAISE_APPLICATION_ERROR(-20000,'Start DATE CANNOT Be GREATER than today's
date');
END IF;
END;
Here STARTDATE is column of CASE table
This trigger starts when start date is greater than today's date but I need it to run when it's out range as given above.
How do I add an specified interval to the sysdate, so that it could work for the above condition?
The logic you are using in your Trigger is completely wrong. You don't need to get the count from the table using :NEW.STARTDATE. This is something what you are looking for.
CREATE OR replace TRIGGER ch
BEFORE INSERT ON cases
FOR EACH ROW
BEGIN
IF ( :NEW.casedate < SYSDATE - INTERVAL '5' year
OR :NEW.casedate > SYSDATE + INTERVAL '7' year ) THEN
RAISE_APPLICATION_ERROR(-20000,
'CASE DATE should be in range: current date - 5 years and current date + 7 years')
;
END IF;
END;
/
EDIT : I have not added TRUNC on the dates because I'm not sure if you want to consider time component as well while considering date range.If you are ok with just considering days, you may use TRUNC(SYSDATE) in place of just SYSDATE. Modify it accordingly as per your business needs.
Another option is to use CHECK constraint. Although Oracle does not allow you to have
use SYSDATE in a check constraint definition, you may create another column( or reuse existing) that defaults to SYSDATE and apply check constraint on that.
ALTER TABLE CASES ADD ( CURR_DATE DATE DEFAULT SYSDATE );
ALTER TABLE CASES ADD CONSTRAINT
RANGE_CHECK CHECK( casedate > CURR_DATE - INTERVAL '5' YEAR
AND casedate < CURR_DATE + INTERVAL '7' YEAR) ENABLE;
Date arithmetic. Oracle Database enables you to perform arithmetic operations on dates and time stamps in several ways:
Add a numeric value to or subtract it from a date, as in SYSDATE + 7; Oracle Database treats the number as the number of days.
Add one date to or subtract it from another, as in l_hiredate - SYSDATE.
Use a built-in function to “move” a date by a specified number of months or to another date in a week.
Here are some examples of date arithmetic with a date and a number (assume in all cases that the l_date variable has been declared as DATE):
Set a local variable to tomorrow’s date:
l_date := SYSDATE + 1;
Move back one hour:
l_date := SYSDATE - 1/24;
Move ahead 10 seconds:
l_date := SYSDATE + 10 / (60 * 60 * 24);
When you add one date to or subtract it from another, the result is the number of days between the two. As a result, executing this block:
DECLARE
l_date1 DATE := SYSDATE;
l_date2 DATE := SYSDATE + 10;
BEGIN
DBMS_OUTPUT.put_line (
l_date2 - l_date1);
DBMS_OUTPUT.put_line (
l_date1 - l_date2);
END;
returns the following output:
10
-10

How to get year with fractional part from date in Oracle?

I need a PL/SQL function that takes a date and returns the year as a number, including a fractional part from the days. For example, 1995-01-01 would be 1995.000 and 2015-11-24 would be 2015.897. This is what I managed to produce:
CREATE OR REPLACE FUNCTION year_fraction (in_date DATE)
RETURN NUMBER
IS
year NUMBER;
days NUMBER;
total_days NUMBER;
BEGIN
year := EXTRACT(YEAR FROM in_date);
days := in_date - TRUNC(in_date, 'YEAR');
total_days := ADD_MONTHS(TRUNC(in_date, 'YEAR'), 12) - TRUNC(in_date, 'YEAR');
RETURN year + days / total_days;
END year_fraction;
It works, but it feels like to much work to solve quite a simple problem. Can this be done in a neater way?
So you want the decimal part as percentage of days passed in current year. It would be a simple date arithmetic to divide the day of the year with total days in that year.
SQL> SELECT EXTRACT(YEAR FROM SYSDATE)
2 ||'.'
3 ||TRUNC(TO_NUMBER(TO_CHAR(SYSDATE, 'DDD'))/
4 (add_months(TRUNC(SYSDATE,'year'), 12) - TRUNC(SYSDATE,'year'))*1000) my_format
5 FROM DUAL;
MY_FORMAT
--------------------------------------------------------------------------------
2015.898
Eventually I ended up using this solution. It is quite similar to what I started out with in the question, but involves fewer function calls and is a bit more condensed.
CREATE OR REPLACE FUNCTION fractional_year (in_date DATE)
RETURN NUMBER
IS
year DATE;
BEGIN
year := TRUNC(in_date, 'YEAR');
-- Year + Days passed so far / Total number of days in year
RETURN EXTRACT(YEAR FROM year) + (in_date - year) / (ADD_MONTHS(year, 12) - year);
END fractional_year;

date difference between two date datetype variable in HH:MM:SI in Oracle [duplicate]

This question already has answers here:
In Oracle, is there a function that calculates the difference between two Dates?
(5 answers)
Closed 8 years ago.
I am facing an issue where I have to take the difference between two date datetype variables in HH:MM:SS AM. For example if date1 stores 23-DEC-2014 02:00:00 PM and date2 stores 24-DEC-2014 02:00:00 PM then date2 - date1 should return 24:00:00.
I tried different to_char and likewise methods.
Can you please suggest what I should do to resolve this issue.
As you have plain DATE, the difference between two dates is expressed in fractional days. Some little arythmetics as explained in the related questions might help.
One other approach would be to cast the difference to an INTERVAL using NUMTODSINTERVAL. However, this does not work out-of-the-box, as (of 11g at least), the TO_CHAR function does not supports correctly INTERVAL.
However, as a workaround that is not provided in the related answers (or do I missed it?), you can cast to INTERVAL DAY TO SECOND using the right precision to achieve (more or less) what you are looking for:
Here is an example
with testdata as (select to_date('12/12/2014 09:00:00','DD/MM/YYYY HH:MI:SS') a,
to_date('10/11/2014 11:30:14','DD/MM/YYYY HH:MI:SS') b from dual)
select a-b "days",
numtodsinterval(a-b, 'DAY') "ds interval",
CAST(numtodsinterval(a-b, 'DAY') AS INTERVAL DAY(3) TO SECOND(0))
-- ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- cast to 3 digit days interval -- no fractional seconds
from testdata
Producing (formatted as rows for display purpose):
days
31.8956712962962962962962962962962962963
ds interval
+000000031 21:29:46.000000000
CAST(NUMTODSINTERVAL(A-B,'DAY') AS INTERVAL DAY(3) TO SECOND(0))
+031 21:29:46
I don't know if/how you can get rid of the leading sign though
Maybe this helps:
CREATE OR REPLACE FUNCTION get_date_diff(p_date1 IN DATE,
p_date2 IN DATE) RETURN VARCHAR2
IS
v_seconds NUMBER;
v_minutes NUMBER;
v_hours NUMBER;
v_time VARCHAR2(20);
BEGIN
v_seconds := (p_date2 - p_date1) * 24 * 60 * 60;
v_hours := FLOOR(v_seconds / 3600);
v_minutes := FLOOR((v_seconds - (v_hours * 3600)) / 60);
v_seconds := FLOOR(v_seconds - (v_hours * 3600) - (v_minutes * 60));
v_time := CASE WHEN v_hours < 100
THEN LPAD(TO_CHAR(v_hours), 2, '0')
ELSE TO_CHAR(v_hours)
END || ':' ||
LPAD(TO_CHAR(v_minutes), 2, '0') || ':' ||
LPAD(TO_CHAR(v_seconds), 2, '0');
RETURN v_time;
END;
/
SAMPLE INPUT
p_date1:=to_date('20/11/2014 11:30:45','DD/MM/YYYY HH:MI:SS')
p_date2 :=to_date('15/12/2014 09:00:00','DD/MM/YYYY HH:MI:SS')
SAMPLE OUTPUT
597:29:15

In Oracle, is there a function that calculates the difference between two Dates?

In Oracle, is there a function that calculates the difference between two Dates? If not, is a way to display the difference between two dates in hours and minutes?
Query:
SELECT Round(max((EndDate - StartDate ) * 24), 2) as MaximumScheduleTime,
Round(min((EndDate - StartDate) * 24), 2) as MinimumScheduleTime,
Round(avg((EndDate - StartDate) * 24), 2) as AveragegScheduleTime
FROM table1
You can subtract two dates in Oracle. The result is a FLOAT which represents the number of days between the two dates. You can do simple arithmetic on the fractional part to calculate the hours, minutes and seconds.
Here's an example:
SELECT TO_DATE('2000/01/02:12:00:00PM', 'yyyy/mm/dd:hh:mi:ssam')-TO_DATE('2000/01/01:12:00:00AM', 'yyyy/mm/dd:hh:mi:ssam') DAYS FROM DUAL
Results in: 1.5
You can use these functions :
1) EXTRACT(element FROM temporal_value)
2) NUMTOYMINTERVAL (n, unit)
3) NUMTODSINTERVAL (n, unit).
For example :
SELECT EXTRACT(DAY FROM NUMTODSINTERVAL(end_time - start_time, 'DAY'))
|| ' days ' ||
EXTRACT(HOUR FROM NUMTODSINTERVAL(end_time - start_time, 'DAY'))
||':'||
EXTRACT(MINUTE FROM NUMTODSINTERVAL(end_time - start_time, 'DAY'))
||':'||
EXTRACT(SECOND FROM NUMTODSINTERVAL(end_time - start_time, 'DAY'))
"Lead Time"
FROM table;
With Oracle Dates, this is pretty
trivial, you can get either TOTAL
(days, hours, minutes, seconds)
between 2 dates simply by subtracting
them or with a little mod'ing you can
get Days/Hours/Minutes/Seconds
between.
http://asktom.oracle.com/tkyte/Misc/DateDiff.html
Also, from the above link:
If you really want 'datediff' in your
database, you can just do something
like this:
SQL> create or replace function datediff( p_what in varchar2,
2 p_d1 in date,
3 p_d2 in date ) return number
4 as
5 l_result number;
6 begin
7 select (p_d2-p_d1) *
8 decode( upper(p_what),
9 'SS', 24*60*60, 'MI', 24*60, 'HH', 24, NULL )
10 into l_result from dual;
11
11 return l_result;
12 end;
13 /
Function created
Q: In Oracle, is there a function that calculates the difference between two Dates?
Just subtract one date expression from another to get the difference expressed as a number of days. The integer portion is the number of whole days, the fractional portion is the fraction of a day. Simple arithmetic after that, multiply by 24 to get hours.
Q: If not, is a way to display the difference between two dates in hours and minutes?
It's just a matter of expressing the duration as whole hours and remainder minutes.
We can go "old school" to get durations in hhhh:mi format using a combination of simple builtin functions:
SELECT decode(sign(t.maxst),-1,'-','')||to_char(floor(abs(t.maxst)/60))||
decode(t.maxst,null,'',':')||to_char(mod(abs(t.maxst),60),'FM00')
as MaximumScheduleTime
, decode(sign(t.minst),-1,'-','')||to_char(floor(abs(t.minst)/60))||
decode(t.minst,null,'',':')||to_char(mod(abs(t.minst),60),'FM00')
as MinimumScheduleTime
, decode(sign(t.avgst),-1,'-','')||to_char(floor(abs(t.avgst)/60))
decode(t.avgst,null,'',':')||to_char(mod(abs(t.avgst),60),'FM00')
as AverageScheduleTime
FROM (
SELECT round(max((EndDate - StartDate) *1440),0) as maxst
, round(min((EndDate - StartDate) *1440),0) as minst
, round(avg((EndDate - StartDate) *1440),0) as avgst
FROM table1
) t
Yeah, it's fugly, but it's pretty fast. Here's a simpler case, that shows better what's going on:
select dur as "minutes"
, abs(dur) as "unsigned_minutes"
, floor(abs(dur)/60) as "unsigned_whole_hours"
, to_char(floor(abs(dur)/60)) as "hhhh"
, mod(abs(dur),60) as "unsigned_remainder_minutes"
, to_char(mod(abs(dur),60),'FM00') as "mi"
, decode(sign(dur),-1,'-','') as "leading_sign"
, decode(dur,null,'',':') as "colon_separator"
from (select round(( date_expr1 - date_expr2 )*24*60,0) as dur
from ...
)
(replace date_expr1 and date_expr2 with date expressions)
let's unpack this
date_expr1 - date_expr2 returns difference in number of days
multiply by 1440 (24*60) to get duration in minutes
round (or floor) to resolve fractional minutes into integer minutes
divide by 60, integer quotient is hours, remainder is minutes
abs function to get absolute value (change negative values to positive)
to_char format model FM00 give two digits (leading zeros)
use decode function to format a negative sign and a colon (if needed)
The SQL statement could be made less ugly using a PL/SQL function, one that takes two DATE arguments a duration in (fractional) days and returns formatted hhhh:mi
(untested)
create function hhhhmi(an_dur in number)
return varchar2 deterministic
is
begin
if an_dur is null then
return null;
end if;
return decode(sign(an_dur),-1,'-','')
|| to_char(floor(abs(an_dur)*24))
||':'||to_char(mod((abs(an_dur)*1440),60),'FM00');
end;
With the function defined:
SELECT hhhhmi(max(EndDate - StartDate)) as MaximumScheduleTime
, hhhhmi(min(EndDate - StartDate)) as MinimumScheduleTime
, hhhhmi(avg(EndDate - StartDate)) as AverageScheduleTime
FROM table1
You can use the months_between function to convert dates to the difference in years and then use between the decimal years you are interested:
CASE
WHEN ( ( MONTHS_BETWEEN( TO_DATE(date1, 'YYYYMMDD'),
TO_DATE(date1,'YYYYMMDD'))/12
)
BETWEEN Age1DecimalInYears AND Age2DecimalInYears
)
THEN 'It is between the two dates'
ELSE 'It is not between the two dates'
END;
You may need to change date format to match the a given date format and verify that 31 day months work for your specific scenarios.
References:
( found on www on 05/15/2015 )
1. Oracle/PLSQL: MONTHS_BETWEEN Function
2. Oracle Help Center - MONTHS_BETWEEN

Resources