Subtracting Dates in Oracle - Number or Interval Datatype?
Ok, I don't normally answer my own questions but after a bit of tinkering, I have figured out definitively how Oracle stores the result of a DATE subtraction.
When you subtract 2 dates, the value is not a NUMBER datatype (as the Oracle 11.2 SQL Reference manual would have you believe). The internal datatype number of a DATE subtraction is 14, which is a non-documented internal datatype (NUMBER is internal datatype number 2). However, it is actually stored as 2 separate two's complement signed numbers, with the first 4 bytes used to represent the number of days and the last 4 bytes used to represent the number of seconds.
An example of a DATE subtraction resulting in a positive integer difference:
select date '2009-08-07' - date '2008-08-08' from dual;
Results in:
DATE'2009-08-07'-DATE'2008-08-08'
---------------------------------
364
select dump(date '2009-08-07' - date '2008-08-08') from dual;
DUMP(DATE'2009-08-07'-DATE'2008
-------------------------------
Typ=14 Len=8: 108,1,0,0,0,0,0,0
Recall that the result is represented as a 2 seperate two's complement signed 4 byte numbers. Since there are no decimals in this case (364 days and 0 hours exactly), the last 4 bytes are all 0s and can be ignored. For the first 4 bytes, because my CPU has a little-endian architecture, the bytes are reversed and should be read as 1,108 or 0x16c, which is decimal 364.
An example of a DATE subtraction resulting in a negative integer difference:
select date '1000-08-07' - date '2008-08-08' from dual;
Results in:
DATE'1000-08-07'-DATE'2008-08-08'
---------------------------------
-368160
select dump(date '1000-08-07' - date '2008-08-08') from dual;
DUMP(DATE'1000-08-07'-DATE'2008-08-0
------------------------------------
Typ=14 Len=8: 224,97,250,255,0,0,0,0
Again, since I am using a little-endian machine, the bytes are reversed and should be read as 255,250,97,224 which corresponds to 11111111 11111010 01100001 11011111. Now since this is in two's complement signed binary numeral encoding, we know that the number is negative because the leftmost binary digit is a 1. To convert this into a decimal number we would have to reverse the 2's complement (subtract 1 then do the one's complement) resulting in: 00000000 00000101 10011110 00100000 which equals -368160 as suspected.
An example of a DATE subtraction resulting in a decimal difference:
select to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS'
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS') from dual;
TO_DATE('08/AUG/200414:00:00','DD/MON/YYYYHH24:MI:SS')-TO_DATE('08/AUG/20048:00:
--------------------------------------------------------------------------------
.25
The difference between those 2 dates is 0.25 days or 6 hours.
select dump(to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS')
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS')) from dual;
DUMP(TO_DATE('08/AUG/200414:00:
-------------------------------
Typ=14 Len=8: 0,0,0,0,96,84,0,0
Now this time, since the difference is 0 days and 6 hours, it is expected that the first 4 bytes are 0. For the last 4 bytes, we can reverse them (because CPU is little-endian) and get 84,96 = 01010100 01100000 base 2 = 21600 in decimal. Converting 21600 seconds to hours gives you 6 hours which is the difference which we expected.
Hope this helps anyone who was wondering how a DATE subtraction is actually stored.
Oracle : how to subtract two dates and get seconds of the result
Your data type is most probably TIMESTAMP
that would explain the rounding problem.
You may workaround it by first casting to DATE
(to get rid of the milliseconds) and that casting it back to TIMESTAMP
(to be able to perform your regexp_substr)
This sample data replays your problem
select opa.*,
NVL(REGEXP_SUBSTR (CAST(opa.end_time AS TIMESTAMP) - CAST(opa.start_time AS TIMESTAMP), '\d{2}:\d{2}:\d{2}'),' ') AS duration,
NVL(REGEXP_SUBSTR (CAST(CAST(opa.end_time AS DATE)AS TIMESTAMP) - CAST(CAST(opa.start_time AS DATE)AS TIMESTAMP), '\d{2}:\d{2}:\d{2}'),' ') AS duration2
from tab opa;
START_TIME END_TIME DURATION DURATION2
------------------------------------ ------------------------------------ --------------------------- ---------------------------
04.05.2021 09:13:07,555000000 +02:00 04.05.2021 09:13:18,111000000 +02:00 00:00:10 00:00:11
How to get Time Differences of Two Dates like x days y hours z seconds t minutes
According to the datetime arithmetic matrix difference of two date
values is a number
, which is the number of days. As a pure number it has no time components. So it should be converted to the interval day to second
with numtodsinterval
function:
with a as (
select
sysdate as dt1,
trunc(sysdate) as dt2
from dual
)
select
numtodsinterval(dt1 - dt2, 'DAY') as interval_
from a
| INTERVAL_ |
| :---------------------------- |
| +000000000 22:38:59.000000000 |
db<>fiddle here
But if you have timestamp, then according to the same matrix the difference will be interval by default:
with a as (
select
sysdate as dt1,
trunc(sysdate) as dt2
from dual
)
select
numtodsinterval(dt1 - dt2, 'DAY') as interval_
from a
| INTERVAL_ |
| :---------------------------- |
| +000000000 22:38:59.000000000 |
db<>fiddle here
Note that interval has no format in to_char
function, so to retrieve hours, minutes, seconds you will need to use extract
function
How can I get the number of days between 2 dates in Oracle 11g?
I figured it out myself. I need
select extract(day from sysdate - to_date('2009-10-01', 'yyyy-mm-dd')) from dual
How to calculate exact hours between two datetime fields?
The 'format' comment on your first query suggests your columns are timestamps, despite the dummy column names, as the result of subtracting two timestamps is an interval. Your second query is implicitly converting both timestamps to dates before subtracting them to get an answer as a number of days - which would be fractional if you weren't truncating them and thus losing the time portion.
You can extract the number of hours from the interval difference, and also 24 * the number of days if you expect it to exceed a day:
extract(day from (date1 - date2)) * 24 + extract(hour from (date1 - date2))
If you want to include fractional hours then you can extract and manipulate the minutes and seconds too.
You can also explicitly convert to dates, and truncate or floor after manipulation:
floor((cast(date1 as date) - cast(date2 as date)) * 24)
db<>fiddle demo
Timestamp subtraction
When you subtract one value that is of timestamp data type from another one, which is also of timestamp data type, result of that subtraction will be of interval data type, not timestamp, so when you are trying to select/assign a value of interval data type into/to a variable that is of timestamp data type, you will inevitably receive the PL/SQL: ORA-00932:
/PLS-00382
error message. A simple solution is to assign the result of timestamp subtraction to a variable of interval data type. To that end you:
Re-declare your
vl_diff
variable as a variable of interval data type:vl_diff interval day to second;
use
extract()
function to extract minutes from the value assigned tovl_diff
variable:extract(minute from vl_diff)
Your second query might look like this:
select case when count(1) > 0 then 1 else 2 end
into vl_val
from sessions
where login_details = p_login
and extract(minute from vl_diff) > 30
Subtract interval from Date in Oracle
You can use SYSDATE
(oracle specific) or CURRENT_DATE
(ANSI) to get the current date/time.
In SQL Developer you can set the date format by going to "Tools" > "Preferences" and selecting the "Database" > "NLS" on the left hand side and then editing the Date Format.
If you want to use a different date format most of the time but in this singular instance want the date in ISO 8601 format (YYYY-MM-DD) then you can use TO_CHAR( date, 'YYYY-MM-DD')
(but it will then return as a string rather than a date).
To add (subtract) days from a date then you can do one of several options (note: SQLFIDDLE has different NLS date formatting perameters on its output that you've specified, but it should give you the idea):
SQL Fiddle
Query 1:
SELECT CURRENT_DATE - INTERVAL '1' DAY,
CURRENT_DATE - 1
FROM DUAL
Results:
| CURRENT_DATE-INTERVAL'1'DAY | CURRENT_DATE-1 |
|-----------------------------|----------------------------|
| November, 12 2015 12:28:40 | November, 12 2015 12:28:40 |
Related Topics
How to Execute a Text File from SQL Query
Update Multiple Rows with One Query
Split String into Rows Oracle SQL
Rolling 90 Days Active Users in Bigquery, Improving Preformance (Dau/Mau/Wau)
How to Get Rid of "Error 1329: No Data - Zero Rows Fetched, Selected, or Processed"
Select Without a from Clause in Oracle
How to Do a Case Sensitive Group By
How to Start Auto Increment from a Specific Point
Solution to "Cannot Perform a Dml Operation Inside a Query"
Select Query with Case Condition and Sum()
Hql: How to Perform an Inner Join on a Subquery
Doesn't Linq to SQL Miss the Point? Aren't Orm-Mappers (Subsonic, etc.) Sub-Optimal Solutions
SQL on Delete Cascade, Which Way Does the Deletion Occur
How to Select Records Without Duplicate on Just One Field in SQL