What is the difference, if any, between DATE and TIMESTAMP(0) in Oracle? Both data types occupy 6 bytes, and contain date with time, but without fractional seconds and a timezone.
The TIMESTAMP was added to Oracle about 20 years after DATE to comply with ANSI standard. TIMESTAMP or TIMESTAMP(6) can hold fractions of seconds, so it is different from DATE, but is there any difference between TIMESTAMP(0) and DATE?
One is a DATE data type and one is a TIMESTAMP data type.
They both take up 7 bytes (century, year-of-century, month, day, hour, minute and second).
The DATE will be implicitly formatted by the NLS_DATE_FORMAT session parameter when it is displayed.
The TIMESTAMP will be implicitly formatted by the NLS_TIMESTAMP_FORMAT session parameter when it is displayed.
For example:
CREATE TABLE table_name (
dt DATE,
ts TIMESTAMP(0)
);
INSERT INTO table_name ( dt, ts ) VALUES ( SYSDATE, SYSTIMESTAMP );
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS';
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS.FF6';
Then:
SELECT dt,
DUMP( dt ),
ts,
DUMP( ts )
FROM table_name;
Outputs:
DT | DUMP(DT) | TS | DUMP(TS)
:------------------ | :---------------------------------- | :------------------------- | :-----------------------------------
2020-10-08T14:21:35 | Typ=12 Len=7: 120,120,10,8,15,22,36 | 2020-10-08T14:21:36.000000 | Typ=180 Len=7: 120,120,10,8,15,22,37
Functions that require a TIMESTAMP input won't perform an implicit CAST from a DATE to a TIMESTAMP.
So:
SELECT FROM_TZ( dt, 'UTC' ) FROM table_name;
SELECT FROM_TZ( CAST( dt AS TIMESTAMP(0) ), 'UTC' ) FROM table_name;
SELECT FROM_TZ( ts, 'UTC' ) FROM table_name;
The first statement raises an ORA-00932: inconsistent datatypes: expected TIMESTAMP got DATE exception but the second and third works.
As mentioned by #WernfriedDomscheit in comments, arithmetic is also different between DATE and TIMESTAMP data types. For example:
SELECT dt - dt,
( dt - dt ) DAY TO SECOND,
ts - ts
FROM table_name
Outputs:
DT-DT | (DT-DT)DAYTOSECOND | TS-TS
----: | :------------------ | :------------------
0 | +00 00:00:00.000000 | +000000000 00:00:00
The top expression is DATE - DATE and the result is the difference in days (expressed as a NUMBER data type). However, the bottom expression is TIMESTAMP - TIMESTAMP and the result is an INTERVAL DAY TO SECOND data type. It is possible to get DATE subtraction to output an INTERVAL DAY TO SECOND data type but, as shown in the middle example, you need to explicitly state that that is the output you require.
Addition to a DATE or TIMESTAMP also has differences. For example:
SELECT dt + 1,
dt + INTERVAL '1' DAY,
ts + 1,
ts + INTERVAL '1' DAY
FROM table_name
Outputs:
DT+1 | DT+INTERVAL'1'DAY | TS+1 | TS+INTERVAL'1'DAY
:------------------ | :------------------ | :------------------ | :-------------------------
2020-10-09T19:52:21 | 2020-10-09T19:52:21 | 2020-10-09T19:52:22 | 2020-10-09T19:52:22.000000
Adding a number or an INTERVAL to both a DATE and a TIMESTAMP is both syntactically correct (but may not be as expected). If you look you will see that the output for adding a number or an interval to a date gives the same output in both cases. However, there is a difference between the output when adding a number to a timestamp compared to adding an interval to a timestamp; this is because you can only add numbers to date values and by adding a number to a timestamp then Oracle has performed an implicit cast from timestamp down to a date so:
SELECT ts + 1 FROM table_name
is effectively performing:
SELECT CAST(ts AS DATE) + 1 FROM table_name
So, if you want to perform arithmetic with a TIMESTAMP data type then use INTERVAL data types rather than numbers.
db<>fiddle here
Related
I have below table in oracle, I want Partition Range by Date in Oracle monthly on MyTimestamp column(Data type is number). Can I partition with this column or do I need another column? If I need a new column, what is the data type of the new column and how do I partition with the new column(convert MyTimestamp to new data type and partition )?
----------------------------------------------------------------------------------------------------
| id | MyTimestamp | Name | etc ...
----------------------------------------------------------------------------------------------------
| 0 | 1657640396 | John | ...
| 1 | 1657638832 | Tom | ...
| 2 | 1657640265 | Tom | ...
| 3 | 1657640292 | John | ...
| 4 | 1657640005 | Jack | ...
--------------------------------------------------------------------------------------------------
You can define a virtual column and define partition key on that:
CREATE TABLE ... (
id NUMBER,
MyTimestamp NUMBER
Name VARCHAR2(100),
etc...
PARTITION_KEY TIMESTAMP(0) GENERATED ALWAYS AS (
CAST(TRUNC(TIMESTAMP '1970-01-01 00:00:00 UTC' + MyTimestamp * INTERVAL '1' SECOND) AS TIMESTAMP(0))
) VIRTUAL
)
PARTITION BY RANGE (PARTITION_KEY)
INTERVAL (INTERVAL '1' MONTH)
(
PARTITION P_INITIAL VALUES LESS THAN (TIMESTAMP '2020-01-01 00:00:00')
);
You could also use MyTimestamp directly, however 2'635'200 (i.e. 30.5 days) seconds is just roughly a month
CREATE TABLE ... (
id NUMBER,
MyTimestamp NUMBER
Name VARCHAR2(100),
etc...
)
PARTITION BY RANGE (MyTimestamp)
INTERVAL (2635200)
(
PARTITION P_INITIAL VALUES LESS THAN (1640991600)
);
If you want to PARTITION by DATE, you need a date column. Below is an example with some dummy data.
When new PARTITIONs are automatically added they will have system GENERATED names. I have code to RENAME them to something meaningful if you like.
In addition, you will probably want to implement a RETENTION period for the PARTITION, how long to keep them around. I also implemented that too.
CREATE TABLE t2 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2022-01-01','YYYY-MM-DD'))
);
/
INSERT into t2 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-07-31')
select dt from dt;
/
By timestamp
CREATE TABLE t3 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt TIMESTAMP)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (1, 'MONTH') ) (
PARTITION OLD_DATA VALUES LESS THAN (TIMESTAMP '2022-01-01 00:00:00.000000')
);
/
INSERT into t3 (dt)
SELECT TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '5' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND
FROM DUAL
CONNECT BY
TIMESTAMP '2022-01-01 00:00:00'
+ (LEVEL - 1) * INTERVAL '5' MINUTE
+ MOD(LEVEL - 1, 10) * INTERVAL '0.1' SECOND < DATE '2022-01-15';
/
I have some zero padded year DATE column values (e.g. 0018-04-30), but not all values. Most are 4 digit years (e.g. 2022). What's the most efficient way of converting to a year with century (i.e. 2018-04-30).
I'm not a Oracle guy, but what I came up with is to use a CASE statement with EXTRACT and ADD_MONTHS functions:
CASE
WHEN EXTRACT(YEAR FROM DateColumn) < 1000 THEN ADD_MONTHS(DateColumn,(12*2000))
ELSE DateColumn
END
Example:
SELECT
TO_CHAR(
CASE
WHEN EXTRACT(YEAR FROM (DATE '18-04-30')) < 1000 THEN ADD_MONTHS((DATE '18-04-30'),(12*2000))
ELSE (DATE '18-04-30')
END,'YYYY-MM-DD') AS CorrectedDate
FROM DUAL ;
Is there a better, more efficient, way of doing this?
Use an INTERVAL literal to add 2000 years to your dates:
SELECT dt + INTERVAL '2000' YEAR(4)
FROM DATE_TABLE
db<>fiddle here
Also, your CASE expression can be a bit simpler:
CASE
WHEN DateColumn < DATE '1000-01-01' THEN DateColumn + INTERVAL '2000' YEAR(4)
ELSE DateColumn
END
You may remove leading zeroes from the date using ltrim function and use RR date format element to convert the result to a date, which was introduced for y2k problem (I presume this is the source of such dates):
with dates(dt) as (
select *
from sys.odcidatelist(
date '0018-04-30',
date '3018-05-30',
date '0070-12-29'
)
)
select
to_date(
ltrim(to_char(dt, 'yyyy-mm-dd'), '0'),
'rr-mm-dd'
) as dt
from dates;
| DT |
| :--------- |
| 2018-04-30 |
| 3018-05-30 |
| 1970-12-29 |
db<>fiddle here
I have a scenario in which for example,my start_date ='12-SEP-2018 00:01:00' and End_date ='13-SEP-2018 14:55:00' . The difference between the 2 dates must be found out in Hours and minutes like'12:20'. This must be achieved in oracle database. I tried using the following logic :
SELECT
24 * (to_date('2009-07-07 22:00', 'YYYY-MM-DD hh24:mi') - to_date(
'2009-07-07 19:30', 'YYYY-MM-DD hh24:mi')) diff_hours
FROM
dual;
I was able to get the hour difference but unable to get minutes along with it.
CREATE TABLE table_name ( start_date DATE, end_date DATE );
INSERT INTO table_name VALUES ( TIMESTAMP '2009-07-07 19:30:00', TIMESTAMP '2009-07-07 22:00:00' );
Then you can subtract one from the other and cast it to a DAY TO SECOND interval and then just EXTRACT the component parts of the time:
SELECT EXTRACT( DAY FROM difference ) AS days,
EXTRACT( HOUR FROM difference ) AS hours,
EXTRACT( MINUTE FROM difference ) AS minutes,
EXTRACT( SECOND FROM difference ) AS seconds
FROM (
SELECT ( end_date - start_date ) DAY TO SECOND AS difference
FROM table_name
);
Outputs:
DAYS | HOURS | MINUTES | SECONDS
---: | ----: | ------: | ------:
0 | 2 | 30 | 0
or you can use arithmetic to calculate the values:
SELECT TRUNC( 24 * ( end_date - start_date ) ) AS hours,
TRUNC( MOD( 24 * 60 * ( end_date - start_date ), 60 ) ) AS minutes,
ROUND( MOD( 24 * 60 * 60 * ( end_date - start_date ), 60 ) ) AS seconds
FROM table_name;
which outputs:
HOURS | MINUTES | SECONDS
----: | ------: | ------:
2 | 30 | 0
db<>fiddle here
Since you want a string value, an alternative based on your query attempt is to add the difference between your two date values (which is a numeric value, the number of days between them, including fractional days) to an arbitrary fixed date; and then convert the result of that to a string:
SELECT to_char(date '0001-01-01'
+ (to_date('2009-07-07 22:00', 'YYYY-MM-DD hh24:mi') - to_date( '2009-07-07 19:30', 'YYYY-MM-DD hh24:mi')),
'HH24:MI') as diff
FROM dual;
DIFF
-----
02:30
If the difference can exceed 24 hours then you need to decide how to report that; if you want to include days as a separate figure then you can still use this approach, but need to subtract one (if your fixed date is the first) from the difference before formatting as a string:
SELECT to_char(date '0001-01-01'
+ (to_date('2009-07-08 22:00', 'YYYY-MM-DD hh24:mi') - to_date( '2009-07-07 19:30', 'YYYY-MM-DD hh24:mi'))
- 1,
'DDD:HH24:MI') as diff
FROM dual;
DIFF
---------
001:02:30
If you want the 'hours' value to be higher instead - e.g. '26:30' in this example - then it gets rather more complicated; I see #MTO has added the 'arithmetic' approach already so I won't repeat that. But then might be better off going down the extract() route (which you should consider anyway as it's more flexible and elegant...)
How to select records from Oracle Database where 2 dates are not in same month and year.
Below table is example, here I want all records where created date and updated date are not in same month and year.
The value for both date field in millisecond (ex.1454738400000) and
Data type of both date field is NUMBER(32).
---------------------------------
id| Created Date | Updated Date |
---------------------------------
1 | 02/26/2018 | 02/26/2017 |
---------------------------------
2 | 03/28/2018 | 03/26/2018 |
---------------------------------
3 | 04/26/2018 | 04/28/2017 |
---------------------------------
4 | 02/26/2018 | 02/26/2016 |
---------------------------------
Have a look at these two options:
select record_id
from your_table
where to_char(created_date, 'mm.yyyy') <> to_char(updated_date, 'mm.yyyy');
select record_id
from your_table
where trunc(created_date, 'yyyy') <> trunc(updated_date, 'yyyy')
and trunc(created_date, 'mm') <> trunc(updated_date, 'mm');
If there's a lot of data involved, consider creating a function-based index(es) on DATE columns.
[EDIT]
If those values really are numbers, then you have to convert them to DATE datatype first, then apply TRUNC function. For example:
SQL> select trunc(to_date(20190226, 'yyyymmdd'), 'yyyy') result from dual;
RESULT
----------
01.01.2019
SQL>
Note that it'll fail if format is wrong, for example 20190231 (which is supposed to be 31st of February 2019) as there aren't 31 days in February.
If possible, change those columns' datatype to DATE.
I hardly try to concat timestamps. Column time, defined as varchar2, contains values like 23:15. Now I want to create a timestamp with today's date and that time, in this example 23.03.18 23:15:00.00000 is expected. The way I'm doing this is
to_timestamp(to_char(trunc(current_date),'ddMMyyyy') ||
to_char(time),'dd.MM.yyyy hh24:mi:ss')
and it works. But when field time has value 06:15, I get the message "hour must be between 0 and 23 ". Whatever I try, it's always this message when time value has leading zero. How can that be corrected?
You don't need to use TRUNC on the current date as the TO_CHAR will extract only the year-to-day components and you also don't need TO_CHAR on the time column as it is already a string. Apart from those simplifications, your query works:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE times ( time ) AS
SELECT '00:00' FROM DUAL UNION ALL
SELECT '06:45' FROM DUAL UNION ALL
SELECT '12:00' FROM DUAL UNION ALL
SELECT '18:59' FROM DUAL UNION ALL
SELECT '23:15' FROM DUAL;
Query 1:
SELECT time,
TO_TIMESTAMP(
TO_CHAR( CURRENT_DATE, 'YYYY-MM-DD' ) || time,
'YYYY-MM-DDHH24:MI'
) AS current_day_time
FROM times
Results:
| TIME | CURRENT_DAY_TIME |
|-------|-----------------------|
| 00:00 | 2018-03-23 00:00:00.0 |
| 06:45 | 2018-03-23 06:45:00.0 |
| 12:00 | 2018-03-23 12:00:00.0 |
| 18:59 | 2018-03-23 18:59:00.0 |
| 23:15 | 2018-03-23 23:15:00.0 |
Works OK for me:
SQL> WITH test
2 AS (SELECT '23:15' time FROM DUAL
3 UNION
4 SELECT '06:15' time FROM DUAL)
5 SELECT TO_TIMESTAMP (
6 TO_CHAR (CURRENT_DATE, 'ddMMyyyy') || time,
7 'dd.MM.yyyy hh24:mi:ss') result
8 FROM test;
RESULT
----------------------------------------------------------------
23.03.18 06:15:00,000000000
23.03.18 23:15:00,000000000
SQL>
Please, post your SQL*Plus session so that we could see what you did.
try other way around: insert a leading 0 if hour < 10
to_timestamp(to_char(trunc(current_date),'ddMMyyyy') ||
to_char(
case when substr(time,1,instr(time,':',1,1)-1)<10
then'0'||time
else time
end),'dd.MM.yyyy hh24:mi:ss')