Oracle 19c - Zero padded year - oracle

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

Related

Oracle: Difference between DATE and TIMESTAMP(0)

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

How to get the date difference between start date and end date in oracle as hours and minutes

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

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.

oracle concat timestmap from current_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')

Oracle Date - How to add years to date

I have a date field
DATE = 10/10/2010
sum = 4 (this are number of years by calculation)
is there a way to add four years to 10/10/2010 and make it
10/10/2014?
Try adding months (12 * number of years) instead. Like this-
add_months(date'2010-10-10', 48)
Use add_months
Example:
SELECT add_months( to_date('10-OCT-2010'), 48 ) FROM DUAL;
Warning
add_months, returns the last day of the resulting month if you input the last day of a month to begin with.
So add_months(to_date('28-feb-2011'),12) will return 29-feb-2012 as a result.
I believe you could use the ADD_MONTHS() function. 4 years is 48 months, so:
add_months(DATE,48)
Here is some information on using the function:
http://www.techonthenet.com/oracle/functions/add_months.php
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1157035034361
You can try this:
someDate + interval '4' year
INTERVAL
I am not sure, if I understood Your question correctly, but
select add_months(someDate, numberOfYears * 12) from dual
might do the trick
One more option apart from ADD_MONTHS
SELECT
SYSDATE,
SYSDATE
+ TO_YMINTERVAL ( '1-0' )
FROM
DUAL;
SYSDATE SYSDATE+TO_YMINTERVAL('1-0')
--------- ----------------------------
29-OCT-13 29-OCT-14
1 row selected.
SELECT
SYSDATE,
SYSDATE
+ TO_YMINTERVAL ( '2-0' )
FROM
DUAL;
SYSDATE SYSDATE+TO_YMINTERVAL('2-0')
--------- ----------------------------
29-OCT-13 29-OCT-15
1 row selected.
SELECT
TO_DATE ( '29-FEB-2004',
'DD-MON-YYYY' )
+ TO_YMINTERVAL ( '1-0' )
FROM
DUAL
*
Error at line 4
ORA-01839: date not valid for month specified
But the last one is illegal since there is no 29th day of February in 2005, hence it fails on leap year cases (Feb 29)
Read the documentation for the same
SELECT TO_CHAR(SYSDATE,'YYYY')-2 ANO FROM DUAL

Resources