Wrong difference in minutes in Oracle (DATE2-DATE1)*24*60 - oracle

I'm trying to calculate difference in minutes between two dates in Oracle with this testing query:
SELECT
(DATE2-DATE1)*24*60 DIFFINMINUTES
FROM
(
SELECT
TO_DATE('2014-06-06 10:30:00', 'YYYY-MM-DD HH24:MI:SS') DATE1,
TO_DATE('2014-06-06 11:25', 'YYYY-MM-DD HH24:MI') DATE2
FROM DUAL
);
The expected result should be 55 minutes but I'm getting 54,99999999.
ROUNDing this value gets the job done but I really wants to understand why this calculation was this behavior.

DATE2 - DATE1 Oracle calculates the difference in days. That's why some inaccuracy may occur.
Just an example with timestamps
select inter,
extract (day from inter) days,
extract (hour from inter) hours,
extract (minute from inter) minutes,
extract (second from inter) seconds
from
(select
cast(date2 as timestamp) - cast(date1 as timestamp) inter
FROM
(
SELECT
TO_DATE('2014-06-06 10:30:00', 'YYYY-MM-DD HH24:MI:SS') DATE1,
to_date('2014-06-06 11:25', 'YYYY-MM-DD HH24:MI') date2
from dual
));
Substraction of timestamps gives you exact INTERVAL

Related

How to calculate the difference of HH:MM:SS between two dates in oracle sql?

I have a table abc as:
-- start_time |end_time | total_time_taken
-- 27.05.2020 00:52:48 |27.05.2020 02:08:33 |
I want to set the value of total_time_taken as the difference of end_time-start_time. in the format "HH:MM:SS".I searched the similar topic but didnot find the exact answer.
My expected output is like : 01:44:12 (HH:MM:SS)
So,i tried :
SELECT To_Char(end_time,'HH24:MM:SS'),To_Char(start_time,'HH24:MM:SS'),
To_Char(end_time,'HH24:MM:SS')-To_Char(start_time,'HH24:MM:SS') FROM abc;
The datatypes of start_time,end_time,total_time_taken is DATE.Please help me to find the solution.
If you cast those dates as timestamps, you can easily subtract them and see relatively nice result:
SQL> with test (st, et) as
2 (select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
4 from dual
5 )
6 select cast(et as timestamp) - cast(st as timestamp) diff
7 from test;
DIFF
--------------------------------------------------------------------------
+000000000 01:15:45.000000
SQL>
If you want to format it as you wanted (note that mm format mask is for months; mi is for minutes), then you could do some extracting - again from timestamp (won't work for date):
SQL> with test (st, et) as
2 (select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
4 from dual
5 ),
6 diff as
7 (select cast(et as timestamp) - cast(st as timestamp) diff
8 from test
9 )
10 select extract(hour from diff) ||':'||
11 extract(minute from diff) ||':'||
12 extract(second from diff) diff
13 from diff;
DIFF
-------------------------------------------------------------------------
1:15:45
SQL>
You can further make it pretty (e.g. two digits for hours, using LPAD function). Or, you can even write your own function which will actually work on difference of DATE datatype values, do some calculations (using trunc function, subtractions, whatnot), but the above looks pretty elegant if compared to a home-made function.
The answer by Littlefoot is perfectly fine. This answer is just to show there is more than one way to get the result.
First, we can subtract one date from another and get the difference in days, then convert that difference to an interval.
with test (st, et) as
(select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
from dual
)
select numtodsinterval(et-st, 'day') diff
from test;
Then, since we can't control interval formatting directly, we can add DIFF to an arbitrary date and then use built-in date formatting.
with test (st, et) as
(select to_date('27.05.2020 00:52:48', 'dd.mm.yyyy hh24:mi:ss'),
to_date('27.05.2020 02:08:33', 'dd.mm.yyyy hh24:mi:ss')
from dual
)
select to_char(date '1-1-1' + numtodsinterval(et-st, 'day'), 'hh24:mi:ss') diff
from test;
DIFF
--------
01:15:45

date format picture ends before converting entire input string in Oracle

I have a PD_HEAT_DATA table with HEATID and HEATDEPARTURE_ACT columns. Data type of HEATDEPARTURE column is VARCHAR2 and it holds timestamps in the format 2019-07-28 23:11:11,359.
My requirement is to retrieve the records from PD_HEAT_DATA table between 6 AM of today and 6 AM of next day.
Sample Data:
HeatID HEATDEPARTURE_ACT
0001024002 2019-07-29 00:46:42,115
0001024003 2019-07-29 06:46:42,115
0001024004 2019-07-29 23:46:42,115
0001024003 2019-07-30 00:06:42,115
0001024004 2019-07-30 04:46:42,115
I have tried following code but it is not giving desired result:
select heatid, HEATDEPARTURE_ACT from hd_heat_data where to_date(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS') >= trunc(sysdate
-1) + 6/24 and
to_date(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS') < trunc(sysdate) + 6/24
I have tried following code but it is not giving desired result:
select heatid, HEATDEPARTURE_ACT from hd_heat_data where to_date(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS') >= trunc(sysdate
-1) + 6/24 and
to_date(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS') < trunc(sysdate) + 6/24
You have fractional seconds at the end of your HEATDEPARTURE_ACT strings which you need to trim off prior to converting the string to a date:
select heatid, HEATDEPARTURE_ACT
from hd_heat_data
where TO_DATE(REGEXP_SUBSTR(HEATDEPARTURE_ACT, '[^,]*'),
'YYYY-MM-DD HH24:MI:SS') BETWEEN trunc(sysdate-1) + INTERVAL '6' HOUR
AND trunc(sysdate) + INTERVAL '6' HOUR - INTERVAL '1' SECOND
dbfiddle here
(Note that I altered the dates in the fiddle data so results would be produced)
You need to change your format provided in TO_TIMESTAMP as TO_TIMESTAMP(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS,FF')
SELECT
HEATID,
HEATDEPARTURE_ACT
FROM
HD_HEAT_DATA
WHERE
TO_TIMESTAMP(HEATDEPARTURE_ACT, 'YYYY-MM-DD HH24:MI:SS,FF')
BETWEEN TRUNC(SYSDATE - 1) + INTERVAL '6' HOUR AND TRUNC(SYSDATE) +
INTERVAL '6' HOUR - INTERVAL '1' SECOND
See the demo of reproduction of the issue and resolution, HERE
Cheers!!

Using "today" and "two days before" in date with Oracle

I have the following WHEREclause in a query:
SELECT ...
FROM ...
WHERE IMPORT_DATE between
to_date('2018-03-16 00:00:00', 'yyyy-mm-dd hh24:mi:ss') and
to_date('2018-03-16 23:59:59', 'yyyy-mm-dd hh24:mi:ss')
And I would like to write two new queries:
One with this same clause but using "today" instead of "2018-03-16".
Another with the same clause but using "the day before yesterday" (today - 2) instead of "2018-03-16".
How can I do this in Oracle?
As Oracle has no real DATE data type and always includes a time, it's usually better to not use between for conditions like that, but to use >= together with < compared to midnight the next day.
To find the rows from "today" use:
SELECT ...
FROM ...
WHERE import_date >= trunc(sysdate)
AND import_date < trunc(sysdate) + 1;
or:
SELECT ...
FROM ...
WHERE import_date >= trunc(sysdate) - 2
AND import_date < (trunc(sysdate) - 2) + 1;
The parentheses aren't really required in the second expression, they are just there to document that it's the same expression as the first one.
select trunc(sysdate) from dual;
Returns today's date without any time
select trunc(sysdate) - interval '2' day from dual;
Returns the day 2 days before today without time.
You can also use month, hour, year etc instead of day.

Extract local hour from timestamp with time zone

I'm trying to extract the local hour from a timestamp with time zone, but as I am new to working with this data type, I get unexpected results.
I was expecting 22 as output for every row below.
with my_data as(
select to_timestamp_tz(ts, 'yyyy-mm-dd hh24:mi:ss tzh:tzm') as tsz
from (select '2017-12-07 22:23:24 +' || lpad(level, 2, '0') || ':00' as ts
from dual connect by level <= 10
)
)
select dbtimezone
,sessiontimezone
,tsz
,extract(hour from tsz)
from my_data;
Why does this happen, and what do I need to do to extract the local hour from a timestamp with time zone?
Check documentation of EXTRACT(datetime):
When extracting from a datetime with a time zone value, the value returned is in UTC.
Use TO_CHAR(tsz, 'HH24') or EXTRACT(hour from cast(tsz as timestamp)) if you like to get local hours.

oracle convert unix epoch time to date

The context is that there is an existing application in our product which generates and sends the EPOCH number to an existing oracle procedure & vice versa. It works in that procedure using something like this
SELECT UTC_TO_DATE (1463533832) FROM DUAL
SELECT date_to_utc(creation_date) FROM mytable
When I tried these queries it does work for me as well with Oracle 10g server (and oracle sql developer 4.x if that matters).
In the existing procedure the requirement was to save the value as date itself (time component was irrelevant), however in the new requirement I have to convert unix EPOCH value to datetime (at the hours/mins/seconds level, or better in a specific format such as dd-MMM-yyyy hh:mm:ss) in an oracle query. Strangely I am unable to find any documentation around the UTC_TO_DATE and DATE_TO_UTC functions with Google. I have looked around at all different questions on stackoverflow, but most of them are specific to programming languages such as php, java etc.
Bottom line, how to convert EPOCH to that level of time using these functions (or any other functions) in Oracle query? Additionally are those functions I am referring could be custom or specific somewhere, as I don't see any documentation or reference to this.
To convert from milliseconds from epoch (assume epoch is Jan 1st 1970):
select to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60 / 1000) * 1322629200000
from dual;
11/30/2011 5:00:00 AM
To convert that date back to milliseconds:
select (to_date('11/30/2011 05:00:00', 'MM/DD/YYYY HH24:MI:SS') - to_date('19700101', 'YYYYMMDD')) * 24 * 60 * 60 * 1000
from dual;
1322629200000
If its seconds instead of milliseconds, just omit the 1000 part of the equation:
select to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60 ) * 1322629200
from dual;
select (to_date('11/30/2011 05:00:00', 'MM/DD/YYYY HH24:MI:SS') - to_date('19700101', 'YYYYMMDD')) * 24 * 60 * 60
from dual;
Hope that helps.
Another option is to use an interval type:
SELECT TO_TIMESTAMP('1970-01-01 00:00:00.0'
,'YYYY-MM-DD HH24:MI:SS.FF'
) + NUMTODSINTERVAL(1493963084212/1000, 'SECOND')
FROM dual;
It has this advantage that milliseconds won't be cut.
If your epoch time is stored as an integer.....
And you desire the conversion to Oracle date format.
Step 1-->
Add your epoch date (1462086000) to standard 01-jan-1970. 86400 is seconds in a 24 hour period.
*Select TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 from dual*
**output is 5/1/2016 7:00:00 AM**
Step 2--> Convert it to a CHAR . This is needed for formatting before additional functions can be applied.
*Select TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss') from dual*
output is 2016-05-01 07:00:00
Step 3--> Now onto Timestamp conversion
Select to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss') from dual
output is 5/1/2016 7:00:00.000000000 AM
Step 4--> Now need the TimeZone, usage of UTC
Select from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') from dual
output is 5/1/2016 7:00:00.000000000 AM +00:00
Step 5--> If your timezone need is PST
Select from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') at time zone 'America/Los_Angeles' TZ from dual
output is 5/1/2016 12:00:00.000000000 AM -07:00
Step 6--> Format the PST Timezone timestamp.
Select to_Char(from_tz(to_timestamp(TO_CHAR(TO_DATE('01-jan-1970', 'dd-mon-yyyy') + 1462086000/86400 ,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss'),'UTC') at time zone 'America/Los_Angeles' ,'DD-MON-YYYY HH24:MI:SS') TZ from dual
output is 01-MAY-2016 00:00:00
Step 7--> And finally, if your column is date datatype
Add to_DATE to the whole above Select.
Here it is for both UTC/GMT and EST;
GMT select (to_date('1970-01-01 00','yyyy-mm-dd hh24') +
(1519232926891)/1000/60/60/24) from dual;
EST select new_time(to_date('1970-01-01 00','yyyy-mm-dd hh24') +
(1519232926891)/1000/60/60/24, 'GMT', 'EST') from dual;
I thought somebody would be interested in seeing an Oracle function version of this:
CREATE OR REPLACE FUNCTION unix_to_date(unix_sec NUMBER)
RETURN date
IS
ret_date DATE;
BEGIN
ret_date:=TO_DATE('19700101','YYYYMMDD')+( 1/ 24/ 60/ 60)*unix_sec;
RETURN ret_date;
END;
/
I had a bunch of records I needed dates for so I updated my table with:
update bobfirst set entered=unix_to_date(1500000000+a);
where a is a number between 1 and 10,000,000.
A shorter method to convert timestamp to nanoseconds.
SELECT (EXTRACT(DAY FROM (
SYSTIMESTAMP --Replace line with desired timestamp --Maximum value: TIMESTAMP '3871-04-29 10:39:59.999999999 UTC'
- TIMESTAMP '1970-01-01 00:00:00 UTC') * 24 * 60) * 60 + EXTRACT(SECOND FROM
SYSTIMESTAMP --Replace line with desired timestamp
)) * 1000000000 AS NANOS FROM DUAL;
NANOS
1598434427263027000
A method to convert nanoseconds to timestamp.
SELECT TIMESTAMP '1970-01-01 00:00:00 UTC' + numtodsinterval(
1598434427263027000 --Replace line with desired nanoseconds
/ 1000000000, 'SECOND') AS TIMESTAMP FROM dual;
TIMESTAMP
26/08/20 09:33:47,263027000 UTC
As expected, above methods' results are not affected by time zones.
A shorter method to convert interval to nanoseconds.
SELECT (EXTRACT(DAY FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval --Maximum value: INTERVAL '+694444 10:39:59.999999999' DAY(6) TO SECOND(9) or up to 3871 year
) * 24 * 60) * 60 + EXTRACT(SECOND FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval
))) * 1000000000 AS NANOS FROM DUAL;
NANOS
1598434427263027000
A method to convert nanoseconds to interval.
SELECT numtodsinterval(
1598434427263027000 --Replace line with desired nanoseconds
/ 1000000000, 'SECOND') AS INTERVAL FROM dual;
INTERVAL
+18500 09:33:47.263027
As expected, millis, micros and nanos are converted and reverted, dispite of SYSTIMESTAMP doesn't have nanosecounds information.
Replace 1000000000 by 1000, for example, if you'd like to work with milliseconds instead of nanoseconds.
I've tried some of posted methods, but almost of them are affected by the time zone or result on data loss after revertion, so I've decided do post the methods that works for me.

Resources