How are dates stored in Oracle? - oracle

How are dates stored in Oracle? For example I know most systems use Epoch time to determine what time it is. By calculating how many seconds away from January 1st 1970. Does Oracle do this as well?
The reason I am asking this is I noticed if you take two dates in Oracle and subtract them you get a floating point of how many days are between.
Example
(Sysdate - dateColumn)
would return something like this (depending on the time)
3.32453703703703703703703703703703703704
Now is Oracle doing the conversion and spitting that format out, or does Oracle store dates with how many days it is away from a certain time frame? (Like Epoch time)

There are two types 12 and 13
http://oraclesniplets.tumblr.com/post/1179958393/my-oracle-support-oracle-database-69028-1
Type 13
select dump(sysdate) from dual;
Typ=13 Len=8: 220,7,11,26,16,41,9,0
The format of the date datatype is
Byte 1 - Base 256 year modifier : 220
2 - Base 256 year : 256 * 7 = 1792 + 220 = 2012
3 - Month : 11
4 - Day : 26
5 - Hours : 16
6 - Minutes : 41
7 - Seconds : 09
8 - Unused
2012-11-26 16:41:09
Type 12
select dump(begindate) from tab;
Typ=12 Len=7: 100,112,2,7,1,1,1
The format of the date datatype is
byte 1 - century (excess 100) 100 - 100 = 00
byte 2 - year (excess 100) 112 - 100 = 12
byte 3 - month = 2
byte 4 - day = 7
byte 5 - hour (excess 1) 1 - 1 = 0
byte 6 - minute (excess 1) 1 - 1 = 0
byte 7 - seconds (excess 1) 1 - 1 = 0
0012-02-07 00:00:00

From the manual at http://docs.oracle.com/cd/E11882_01/server.112/e26088/sql_elements001.htm#sthref151
For each DATE value, Oracle stores the following information: year, month, day, hour, minute, and second
So apparently it's not storing an epoch value which is also confirmed by this chapter of the manual:
The database stores dates internally as numbers. Dates are stored in fixed-length fields of 7 bytes each, corresponding to century, year, month, day, hour, minute, and second

How are dates stored in Oracle?
The two data types 12 and 13 are for two different purposes.
Type 12 - Dates stored in table
Type 13 - Date returned by internal date functions like SYSDATE/CURRENT_DATE, also when converting a string literal into date using TO_DATE or ANSI Date literal DATE 'YYYY-MM-DD'.
Test cases:
Basic table setup for type 12:
SQL> CREATE TABLE t(col DATE);
Table created.
SQL> INSERT INTO t SELECT SYSDATE FROM dual;
1 row created.
SQL> COMMIT;
Commit complete.
Check the different cases:
SQL> SELECT DUMP(col) FROM t;
DUMP(COL)
--------------------------------------------------------------------------------
Typ=12 Len=7: 120,116,3,17,18,6,55
SQL> SELECT DUMP(SYSDATE) FROM dual;
DUMP(SYSDATE)
--------------------------------------------------------------------------------
Typ=13 Len=8: 224,7,3,17,17,5,54,0
SQL> SELECT DUMP(CURRENT_DATE) FROM dual;
DUMP(CURRENT_DATE)
--------------------------------------------------------------------------------
Typ=13 Len=8: 224,7,3,17,17,14,20,0
SQL> SELECT DUMP(TO_DATE('17-DEC-1980 12:12:12','DD-MON-YYYY HH24:MI:SS')) FROM dual;
DUMP(TO_DATE('17-DEC-198012:12:12','
------------------------------------
Typ=13 Len=8: 188,7,12,17,12,12,12,0
Using ANSI Date literal, just like TO_DATE:
SQL> SELECT DUMP(DATE '2016-03-17') FROM dual;
DUMP(DATE'2016-03-17')
--------------------------------
Typ=13 Len=8: 224,7,3,17,0,0,0,0
SQL> INSERT INTO t SELECT to_date('17-DEC-1980 12:13:14','DD-MON-YYYY HH24:MI:SS') FROM dual;
1 row created.
SQL> COMMIT;
Commit complete.
SQL> SELECT DUMP(col) FROM t;
DUMP(COL)
--------------------------------------------------------------------------------
Typ=12 Len=7: 120,116,3,17,18,6,55
Typ=12 Len=7: 119,180,12,17,13,14,15
SQL>
As you can see, while storing a date in the table, it uses type 12. The second type 13 is used when converting a string literal into date using date functions or when date returned by internal date functions like SYSDATE/CURRENT_DATE.

Related

Charts in Oracle Apex

Hi everyone I wanna ask u about how I can bring data last 24 hours into bar charts, is there any methods to make it please
I have this table without data
datetime
clientchannel
servicename
service_count
13_02_2022 9:35
*****
notification
2
It is a WHERE clause you need, I presume. Something like this:
select ...
from your_table
where datetime >= sysdate - 1;
Why? Because - when you subtract a number from DATE datatype value in Oracle - it subtracts that many days.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select sysdate right_now,
2 sysdate - 1 yesterday
3 from dual;
RIGHT_NOW YESTERDAY
------------------- -------------------
13.02.2022 11:01:34 12.02.2022 11:01:34
SQL>
If you store date values as strings (which means that DATETIME column is declared as e.g. VARCHAR2(20), and that's really bad idea), then you first have to convert it to a valid date datatype value - use TO_DATE function with appropriate format mask:
where to_date(datetime, 'dd_mm_yyyy hh24:mi') >= sysdate - 1
[EDIT] If you want to go 60 minutes back, then subtract that many minutes:
SQL> select sysdate right_now,
2 sysdate - interval '60' minute an_hour_ago
3 from dual;
RIGHT_NOW AN_HOUR_AGO
------------------- -------------------
14.02.2022 07:09:30 14.02.2022 06:09:30
SQL>

Oracle - Extract Year from date result in 0 [duplicate]

This question already has an answer here:
Trying to export a Oracle via PL/SQL gives a date of 0000-00-00
(1 answer)
Closed 1 year ago.
Duplicate of Trying to export a Oracle via PL/SQL gives a date of 0000-00-00 as mentioned by #AlexPoole
I have a table with different dates in it, however, when I try to extract year, a single row returns an incorrect result.
This incorrect result happens when I execute the following (note that TO_DATE and FROM_DATE are both of data_type DATE):
select
TO_DATE
,EXTRACT(YEAR FROM TO_DATE) as "TO_YEAR"
,EXTRACT(MONTH FROM TO_DATE) as "TO_MONTH"
,EXTRACT(DAY FROM TO_DATE) as "TO_DAY"
,FROM_DATE
,EXTRACT(YEAR FROM FROM_DATE) as "FROM_YEAR"
,EXTRACT(MONTH FROM FROM_DATE) as "FROM_MONTH"
,EXTRACT(DAY FROM FROM_DATE) as "FROM_DAY"
,DUMP(FROM_DATE, 1016) as FROM_DUMP
,to_char(FROM_DATE, 'SYYYY-MM-DD HH24:MI:SS') FROM_STRING
from SomeTable
The incorrect result is (date format is YY-MM-DD):
TO_DATE TO_YEAR TO_MONTH TO_DAY FROM_DAT FROM_YEAR FROM_MONTH FROM_DAY FROM_DUMP FROM_STRING
-------- ---------- ---------- ---------- -------- ---------- ---------- ---------- ----------------------------- --------------------
00-02-01 2000 2 1 01-02-01 0 2 1 Typ=12 Len=7: 64,64,2,1,1,1,1 00000-00-00 00:00:00
My question is why does FROM_YEAR return a zero and not 2001?
A DATE is stored in 7-bytes using:
century + 100
year-of-century + 100
month + 0
day + 0
hour + 1
minute + 1
second + 1
Looking at the output of DUMP, which is Typ=12 Len=7: 64,64,2,1,1,1,1 then you have:
Century = -36
Year-of-century = -36
Month = February
Day = 1
Hour = 0
Minute = 0
Year = 0
Which would make your date midnight of 1st February 3636 BC.
Unless you intended to use dates from ancient history then it would suggest that somewhere in your application some corrupted data has been stored.
However, something else appears to be going on as that is a valid date that can be stored and TO_CHAR should work.
CREATE TABLE table_name ( dt ) AS
SELECT DATE '-3636-02-01'FROM DUAL;
SELECT TO_CHAR( dt, 'SYYYY-MM-DD HH24:MI:SS' ) AS dt_string,
DUMP( dt )
FROM table_name;
Outputs:
DT_STRING
DUMP(DT)
-3636-02-01 00:00:00
Typ=12 Len=7: 64,64,2,1,1,1,1
db<>fiddle here
and the DUMP matches your data but the TO_CHAR output is valid whereas yours is zeros.

Use EPOCH time for timestamp to get records within 1 minute

I was curious to see how in Oracle 12c you can take a timestamp datatype and convert the records into EPOCH time to make them a number and then use that number to find any records within that date column that are within 1 minute of each other (assuming the same day if needed, or simply any calculations within 1 minute).
I tried the following but got an ORA-01873: the leading precision of the interval is too small error.
select (sold_date - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS'))*86400 as epoch_sold_date from test1;
What is SOLD_DATE? For e.g. SYSDATE (function that returns DATE datatype), your code works OK.
SQL> select (sysdate
2 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
3 ) * 86400 as epoch_sold_date
4 from dual;
EPOCH_SOLD_DATE
---------------
1600807918
SQL>
As SOLD_DATE is a timestamp, but - it appears that fractions of a second aren't or special interest to you, cast it to DATE:
select (cast (systimestamp as date) --> this
- to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
) * 86400 as epoch_sold_date
from dual;
Saying that you get the same result for all rows: well, I don't, and you shouldn't either if SOLD_DATE differs.
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 00:00:00.000000' from dual union all
3 select timestamp '2015-03-18 00:00:00.000000' from dual
4 )
5 select sold_date,
6 (cast (sold_date as date)
7 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
8 ) * 86400 as epoch_sold_date
9 from test;
SOLD_DATE EPOCH_SOLD_DATE
------------------------------ ---------------
22.09.20 00:00:00,000000000 1600732800
18.03.15 00:00:00,000000000 1426636800
SQL>
One more edit: when you subtract two timestamps, result is interval day to second. If you extract minutes from it, you get what you wanted:
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 10:15:00.000000' from dual union all
3 select timestamp '2015-03-18 08:05:00.000000' from dual
4 )
5 select sold_date,
6 lead(sold_date) over (order by sold_date) next_sold_date,
7 --
8 lead(sold_date) over (order by sold_date) - sold_date diff,
9 --
10 extract(day from lead(sold_date) over (order by sold_date) - sold_date) diff_mins
11 from test
12 order by sold_date;
SOLD_DATE NEXT_SOLD_DATE DIFF DIFF_MINS
------------------------------ ------------------------------ ------------------------------ ----------
18.03.15 08:05:00,000000000 22.09.20 10:15:00,000000000 +000002015 02:10:00.000000000 2015
22.09.20 10:15:00,000000000
SQL>
In your case, you'd check whether extracted minutes value is larger than 1 (minute).
If you just want to see how many minutes are there between two timestamps, then
cast them to dates
subtract those dates (and you'll get number of days)
multiply it by 24 (as there are 24 hours in a day) and by 60 (as there are 60 minutes in an hour)
Something like this:
SQL> with test (date_1, date_2) as
2 (select timestamp '2020-09-22 10:15:00.000000',
3 timestamp '2020-09-22 08:05:00.000000' from dual
4 )
5 select (cast(date_1 as date) - cast(date_2 as date)) * 24 * 60 diff_minutes
6 from test;
DIFF_MINUTES
------------
130
SQL>
If you are just looking to compare dates and find rows that are within one minute of each other, you do not need to use epoch time. There are several solutions to this problem on this thread.

Insert Birthdate with only YY format for period before 2000 Oracle DB

I have afile where i recieve Birthdates and insert them into my Database.
the format is like the following
03-JUN-52
I use the following script to insert the date
update data."PersonBDates" set BIRTHDATE = to_date('13-SEP-47', 'DD-MON-YY');
and i also used
update data."PersonBDates" set BIRTHDATE = to_date('13-SEP-47', 'DD-MON-RR');
but when i check if find it 2074 not 1947.
How to insert this date into my oracle database?
Generally speaking, RR should work, but - not in all cases. You'll have to fix data first because RR will return different values:
for years from 00 to 49 you'll get this century, 20xx, while
50 to 99 will return previous century, 19xx
Here's an example:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select
2 to_date('03-07-52', 'dd-mm-rr') rr1,
3 to_date('03-07-52', 'dd-mm-yy') yy1 ,
4 --
5 to_date('03-07-47', 'dd-mm-rr') rr2,
6 to_date('03-07-47', 'dd-mm-yy') yy2
7 from dual;
RR1 YY1 RR2 YY2
---------- ---------- ---------- ----------
03.07.1952 03.07.2052 03.07.2047 03.07.2047
SQL>
As you can see, both RR and YY format mask for year 47 return 2047.
What to do? Concatenate 19 to all years, e.g.
SQL> with test (col) as
2 (select '03-07-52' from dual union all
3 select '03-07-47' from dual
4 )
5 select col,
6 to_date(substr(col, 1, 6) || '19' || substr(col, -2), 'dd-mm-rrrr') result
7 ---------------- ---------------
8 -- this is "03-07-" "19" the last 2 digits
9 --
10 from test;
COL RESULT
-------- ----------
03-07-52 03.07.1952
03-07-47 03.07.1947
SQL>
[EDIT]
If your current inserting script works OK - which I doubt, regarding error code you mentioned in a comment:
ORA-01858: a non-numeric character was found where a numeric was expected
which means that not all input data have the same, expected & correct format of DD-MON-YY, then a simple way to fix birthdates might be this:
subtract 100 years from all dates whose year is larger than 2000
Here's how:
SQL> create table test (birthdate date);
Table created.
SQL> insert into test
2 select to_date('03-07-52', 'dd-mm-rr') from dual union all
3 select to_date('03-07-47', 'dd-mm-rr') from dual;
2 rows created.
SQL> select * from test;
BIRTHDATE
----------
03.07.1952
03.07.2047
SQL> update test set
2 birthdate = add_months(birthdate, -100 * 12)
3 where extract (year from birthdate) > 2000;
1 row updated.
SQL> select * from test;
BIRTHDATE
----------
03.07.1952
03.07.1947
SQL>
You can modify that, of course, if there's someone who actually was born in 2000 or later.
As of error you got (ORA-01858), well, fixing it depends on how exactly you're entering those values into a table.
if it was a SQL*Loader, invalid values would be rejected and stored into the .bad file and you could fix them and reprocess them later
if it was using an external tables, you could use a where clause and omit invalid rows; for example, use regexp_like
Or, your best option is to make sure that all input values are valid dates. Then any option you choose (I mentioned previously) would work without ORA-xxxxx errors.
Alternate way of concatenating 19 to all years, as Littlefoot suggested.
to_date(regexp_replace('13-SEP-47', '([0-9]+$)', '19\1'), 'DD-MON-YYYY')
I would suggest to implement the solution where 01 is not considered as 1901 but 2001 or something similar (I assume that birthday year is not 1901 for any person in your system).
Case when substr(col, -2) < to_char(sysdate,'YY')
then to_date(col, 'DD-MON-YY')
else to_date(substr(col, 1, 6) || '19' || substr(col, -2), 'dd-mm-rrrr'
End
Cheers!!

How to extract time from a column and subtract from a custom time in Oracle

I would like to compare two time values. The first time value is a custom time which reprsents the start time, for example the column name is Business_Start_time and set to 6:00:00 am. I would also like to extract the time only from a column in Oracle which is a date field that looks like '5/1/2019 12:57:19 PM' and is called 'Completed_Date_Time'. The purpose of this is to compare the businses start date to the time a file was completed. I've tried to convert the 'Completed_Date_Time' field to 'HH24:MI:SS' format which seems to change the datatype to a char(8) value which does not allow me to compare two timestamps.
CAST(TO_CHAR(Completed_Date_Time, 'HH:MI:SS AM') AS CHAR(8))
Convert the values to TIMESTAMP and then you can subtract the values from the values truncated to the start of the day to get an INTERVAL containing the time since midnight and to get the difference you can subtract.
Oracle Setup:
CREATE TABLE table_name ( Business_Start_time, Completed_Date_Time ) AS
SELECT '6:00:00 AM',
TO_DATE( '5/1/2019 12:57:19 PM', 'DD/MM/YYYY HH12:MI:SS AM' )
FROM DUAL
Query:
SELECT ( completed_time - TRUNC( completed_time ) ) -
( start_time - TRUNC( start_time ) ) AS time_difference
FROM (
SELECT TO_TIMESTAMP( business_start_time, 'HH12:MI:SS AM' ) AS start_time,
CAST( Completed_Date_Time AS TIMESTAMP ) AS completed_time
FROM table_name
)
Output:
| TIME_DIFFERENCE |
| :---------------------------- |
| +000000000 06:57:19.000000000 |
db<>fiddle here
Although you wrote both the question and a comment, I'm still not sure what you have and what you want to get. Sample case would help (create table & insert into).
Meanwhile, a few words about it: when subtracting two DATE datatype values, the result is number of days, which means that - if you want to display it in a format which is easier to read & understand - you have to do some calculations (a day has 24 hours; an hour has 60 mintues; and so forth).
Here's an example:
SQL> create table test
2 (business_Start_time date,
3 completed_date_Time date
4 );
Table created.
SQL> insert into test (business_start_time, completed_date_time) values
2 (to_date('05.01.2019 12:57:19', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('05.01.2019 18:58:20', 'dd.mm.yyyy hh24:mi:ss'));
1 row created.
Simply subtracted, you'd get
SQL> select completed_date_time - business_start_time result from test;
RESULT
----------
,250706019
SQL>
Here's a function which presents such a value in another format, dd:hh:mi (days:hours:minutes) (you can omit days by setting the second parameter to 0):
SQL> create or replace
2 function f_days2ddhhmi (par_broj_dana in number, par_cb_dd in number)
3 return varchar2
4 is
5 /* Converting number of days into dd:hh:mi format
6
7 Date from Date to Diff (days) Retval
8 -------------------- -------------------- -------------- ----------------------------------
9 20.11.2018. 07:00:00 - 20.11.2018. 13:45:00 0,28125 0:06:45 (6 hours 45 minutes)
10 23.10.2018. 07:00:00 - 25.10.2018. 22:12:00 2,63333 2:15:12 (2 daysa 15 hours 12 minutes)
11
12 PAR_BROJ_DANA: 0.28125
13 PAR_CB_DD : display number of days or not? 1 - yes --> 0:06:45
14 0 - no --> 06:45
15 */
16 l_broj_dana number := round (par_broj_dana, 15); -- to avoid 1.99999999999999 days = 1 day 24 hours
17 retval varchar2 (20);
18 begin
19 with podaci
20 as (select trunc (l_broj_dana) broj_dana,
21 round (mod (l_broj_dana * 24, 24), 2) broj_sati
22 from dual)
23 select decode (par_cb_dd,
24 1, lpad (p.broj_dana, 2, '0') || ':',
25 0, null)
26 || lpad (trunc (p.broj_sati), 2, '0')
27 || ':'
28 || lpad (round ( (p.broj_sati - trunc (p.broj_sati)) * 60),
29 2,
30 '0')
31 into retval
32 from podaci p;
33
34 return retval;
35 end f_days2ddhhmi;
36 /
Function created.
Applied to the test table, you'd get
SQL> select f_days2ddhhmi(completed_date_time - business_start_time, 0) result
2 from test;
RESULT
--------------------------------------------------------------------------------
06:01
which means that the difference is 6 hours and 1 minute.
If that's what you asked, see whether you can use it. Feel free to enhance it to seconds etc. if necessary.

Resources