Transform date formats - sql

I have a column in an Oracle table which is varchar and it contains different date formats like ‘dd-mom-yyyy’, ‘dd-mm-yyyy’, ‘dd/mm/yy’.
I need to transform all these formats into a single one, keeping the column as varchar.

As already commented, idea of storing dates as strings is bad. It always is.
You said you want to keep values as they are; it means that you'd actually just want to display values as valid dates. To do that, one option is to create your own function which accepts various values, tries to convert them to date and return it (if it succeeds).
You'd add other formats into the function, once you find them.
For example:
SQL> create or replace function f_format (par_datum in varchar2) return date is
2 retval date;
3 begin
4 -- DD-MON-YYYY
5 begin
6 retval := to_date(par_datum, 'dd-mon-yyyy', 'nls_date_language = english');
7 exception
8 when others then
9
10 -- DD-MM-YYYY
11 begin
12 retval := to_date(par_datum, 'dd-mm-yyyy');
13 exception
14 when others then
15
16 -- DD/MM/YYYY
17 begin
18 retval := to_date(par_datum, 'dd/mm/yyyy');
19 exception
20 when others then
21 null;
22 end;
23 end;
24 end;
25
26 return retval;
27 end;
28 /
Function created.
Let's try it (setting date format, just to know what is what):
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
varchar2 column accepts even garbage; 45-19-2001 looks like date, but it is not.
SQL> select id, datum, f_format(datum) result
2 from test
3 order by id;
ID DATUM RESULT
---------- -------------------- ----------
1 25-OCT-1998 25.10.1998
2 13-02-2022 13.02.2022
3 03/11/1984 03.11.1984
4 45-19-2001
SQL>

From Oracle 12, you can use TO_DATE with the DEFAULT NULL ON CONVERSION ERROR option to convert the string to a date against a known format and TO_CHAR to convert it back to a string and use COALESCE to check multiple formats:
UPDATE table_name
SET date_column = COALESCE(
TO_CHAR(
COALESCE(
TO_DATE(date_column DEFAULT NULL ON CONVERSION ERROR, 'fxDD-MM-RR'),
TO_DATE(date_column DEFAULT NULL ON CONVERSION ERROR, 'DD-Mon-YYYY', 'NLS_DATE_LANGUAGE=English'),
TO_DATE(date_column DEFAULT NULL ON CONVERSION ERROR, 'YYYY-MM-DD'),
TO_DATE(date_column DEFAULT NULL ON CONVERSION ERROR, 'MM-DD-YYYY')
),
'YYYY-MM-DD'
),
date_column
)
Then, for the sample data:
CREATE TABLE table_name (date_column) AS
SELECT '2023-01-25' FROM DUAL UNION ALL
SELECT '01-25-2023' FROM DUAL UNION ALL
SELECT '25-01-23' FROM DUAL UNION ALL
SELECT '25-Jan-2023' FROM DUAL UNION ALL
SELECT 'Wed 25th January 2023' FROM DUAL
After the update, the table contains:
DATE_COLUMN
2023-01-25
2023-01-25
2023-01-25
2023-01-25
Wed 25th January 2023
fiddle
Note: This normalises all the date formats that are tested in the UPDATE statement and will leave the non-matching formats as they were (so you will not lose data).
Even better would be to store the values as a DATE data type and not a string.

Related

how to get name of a month in sql oracle

Display the month name of date “14-jul-15” in full.
I have tried multiple ways but i am not able to get it to display the name of the month.
Use TO_CHAR function with appropriate format mask.
SQL> select to_char(sysdate, 'fm dd-Month-yyyy') result
2 from dual;
RESULT
------------------
25-March-2021
SQL>
Or, if all you have is a string '14-jul-15', the first convert it to a valid date value (using TO_DATE), and then apply TO_CHAR to it.
I'm using then NLS_DATE_LANGUAGE parameter as well because my database speaks Croatian so "jul" is invalid month for me. You might not need it.
SQL> with test (col) as
2 (select '14-jul-15' from dual)
3 select
4 to_char(
5 to_date(col, 'dd-mon-yy', 'nls_date_language = english'),
6 'fm dd-Month-yyyy', 'nls_date_language = english'
7 ) result
8 from test;
RESULT
------------------
14-July-2015
SQL>

Why doesn't date subtraction produce whole numbers when casting TIMESTAMP to DATE?

Normal date subtraction looks like this:
SELECT TO_DATE('12-29-2019') - TO_DATE('12-20-2019') FROM DUAL
/* RESULT: 9 */
When I cast a TIMESTAMP to a DATE, Oracle truncates the hours/minutes/seconds and produces a "whole" DATE value.
SELECT CAST(LOCALTIMESTAMP AS DATE) FROM DUAL
/* RESULT: 12/07/2019 */
But when performing date subtraction with a CAST from a TIMESTAMP, I don't get whole numbers anymore.
SELECT TO_DATE('12-29-2019') - CAST(LOCALTIMESTAMP AS DATE) FROM DUAL
/* RESULT: 21.0999421296296296296296296296296296296 */
Why doesn't date subtraction produce whole numbers when casting TIMESTAMP to DATE in Oracle?
Because what you see is not what you have.
This was your command and result:
SQL> SELECT CAST(LOCALTIMESTAMP AS DATE) FROM DUAL;
CAST(LOC
--------
07.12.19
But, that's just because date format was set as such.
If you alter session and set different format, then you get
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> SELECT CAST(LOCALTIMESTAMP AS DATE) FROM DUAL;
CAST(LOCALTIMESTAMP
-------------------
07.12.2019 22:44:47
SQL>
which is quite different, is it not? And that's why you got decimal number as a result. TRUNC it first to remove time component.

How to make procedure with timestamp typedata

I have tried to make a procedure on PLSQL.
The proble is "ORA-01843: not a valid month" when i to execute code as bellow..
My problem on TIMESTAMP
CREATE OR REPLACE PROCEDURE SP_InsertOvertimes
(
ov_id IN VARCHAR,
dro_id IN DATE,
sto_id IN TIMESTAMP,
eto_id IN TIMESTAMP,
acto_id IN Varchar,
otfk_id IN VARCHAR,
slfk_id IN VARCHAR,
SttsO_id IN VARCHAR,
empfk_id IN NUMBER
)
IS
BEGIN
INSERT into TB_T_Overtimes VALUES (ov_id,dro_id,TO_CHAR(TO_TIMESTAMP(sto_id,'HH24:MI')),eto_id, acto_id,otfk_id,slfk_id,sttso_id,empfk_id);
END;
==== EXECUTE
BEGIN
SP_InsertOvertimes(9, '07/08/2019','01:12', CURRENT_TIMESTAMP(), 'Kerja Lembur Bagai Kuda', '1', '1', 'Kampret' ,1);
END;
Input value '07/08/2019' is not a DATE, it is a string value. Oracle tries implicitly to convert this into a DATE value by using your current session NLS_DATE_FORMAT format. Use TO_DATE or TO_TIMESTAMP function or Datetime Literals for example DATE '2019-08-07'
According to your input parameters dro_id IN DATE, sto_id IN TIMESTAMP, and values '07/08/2019','01:12' you probably misunderstand DATE and TIMESTAMP data type.
Every DATE value has a date and a time component. Even if you provide only the date part, then the time part will be 00:00:00. There is no reason to have separate columns for data and time value.
DATE and TIMESTAMP data type are more or less the same. Both data types have date and time component. The major difference is TIMESTAMP provides also fractional seconds whereas DATE has only full seconds.
You should match datatypes. Here's an example which shows how you might have done that.
Sample table:
SQL> CREATE TABLE tb_t_overtimes(
2 ov_id VARCHAR2(20),
3 dro_id DATE,
4 sto_id TIMESTAMP,
5 eto_id TIMESTAMP,
6 acto_id VARCHAR2(20),
7 otfk_id VARCHAR2(20),
8 slfk_id VARCHAR2(20),
9 sttso_id VARCHAR2(20),
10 empfk_id NUMBER
11 );
Table created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE sp_insertovertimes(
2 ov_id IN VARCHAR,
3 dro_id IN DATE,
4 sto_id IN TIMESTAMP,
5 eto_id IN TIMESTAMP,
6 acto_id IN VARCHAR,
7 otfk_id IN VARCHAR,
8 slfk_id IN VARCHAR,
9 sttso_id IN VARCHAR,
10 empfk_id IN NUMBER
11 )IS
12 BEGIN
13 INSERT INTO tb_t_overtimes VALUES(
14 ov_id,
15 dro_id,
16 sto_id,
17 eto_id,
18 acto_id,
19 otfk_id,
20 slfk_id,
21 sttso_id,
22 empfk_id
23 );
24
25 END;
26 /
Procedure created.
Testing:
SQL> BEGIN
2 sp_insertovertimes
3 ('9',
4 DATE '2019-08-07',
5 to_timestamp('01:12', 'hh24:mi'),
6 current_timestamp,
7 'Kerja Lembur',
8 '1',
9 '1',
10 'Kampret',
11 1);
12 END;
13 /
PL/SQL procedure successfully completed.
Result:
SQL> select * From tb_t_overtimes;
OV_ID DRO_ID
-------------------- -------------------
STO_ID
---------------------------------------------------------------------------
ETO_ID
---------------------------------------------------------------------------
ACTO_ID OTFK_ID SLFK_ID
-------------------- -------------------- --------------------
STTSO_ID EMPFK_ID
-------------------- ----------
9 07.08.2019 00:00:00
01.07.19 01:12:00,000000
19.07.19 14:59:08,320000
Kerja Lembur 1 1
Kampret 1
SQL>
Execute your procedure with the second argument with date'2019-07-08' instead of '07/08/2019' to remove the error, assuming your month is July.
Moreover, the first parameter should be quoted ('9'), because it's defined as varchar (not as numeric) in the table.

Appending the HH:MI:SS to to_date function

I have a table in DB from which I take values from a date field.I store this date value in a cursor. currently the date value is of the format DD:MON:YY. Now I convert this date into character using to_char function so as to append time 00:00:00 to it. now I tried converting back to the date format , but the timestamp is not appended and the date format is not as I have given( format is same as that of the date field in DB).but the to_char function returns the correct format as I have given.
Some of the code snippets are as follows:
Initialized a cursor as
cursor cur is
select to_char(STV_FROM_DATE,'DD:MON:YYYY:')STV_FROM_DATE :---from a table in DB
cur1 cur%rowtype;
begin
open cur;
loop
fetch cur into cur1;
dbms_output.put_line(cur_1.STV_FROM_DATE);
This is giving the value correctly as:
01:JAN:2000:
01:JAN:2000:
01:JAN:2000:
01:JAN:2000:
Now I appended the timestamp 00:00:00 to this and did the to_date operation as follows:
STV_FROM_DATE_BC := cur_1.STV_FROM_DATE;
STV_FROM_DATEBCKUP:=to_date(STV_FROM_DATE_BC,'DD:MM:YY:HH24:MI:SS');
dbms_output.put_line(STV_FROM_DATEBCKUP);
The result obtained is:
01-JAN-00
01-JAN-00
01-JAN-00
Could anyone help me to solve this issue and convert the timestamp appended character to date?
DBMS_OUTPUT is to DISPLAY. so, to display a DATE, you need TO_CHAR.
STV_FROM_DATEBCKUP:=to_date(STV_FROM_DATE_BC,'DD:MM:YY:HH24:MI:SS');
dbms_output.put_line(STV_FROM_DATEBCKUP);
Never use YY, always mention 4 digits YYYY for the complete year. You don't want to introduce the Y2K bug again. Above, you are trying to display the date value, but you did not mention the format model that you want to display. So simply use TO_CHAR along with proper format model.
Reason Without providing a proper format model, your client would just display according to your locale-specific NLS settings. For example, if I just display the sysdate, I would see the format that is mentioned in my NLS_DATE_FORMAT in v$parameters.
SQL> select parameter, value from v$nls_parameters where parameter='NLS_DATE_FORMAT';
PARAMETER VALUE
-------------------- --------------------
NLS_DATE_FORMAT DD-MON-RR
SQL> select sysdate from dual;
SYSDATE
---------
27-JAN-15
SQL>
So, I got 27-JAN-15 as SYSDATE, since my NLS_DATE_FORMAT is DD-MON-RR. You could set it at system level or at session level.
SQL> alter session set NLS_DATE_FORMAT='DD:MM:YYYY:HH24:MI:SS';
Session altered.
SQL> select sysdate from dual;
SYSDATE
-------------------
27:01:2015:11:54:07
SQL>
So, you could either set your NLS_DATE_FORMAT to set the format model that suits you. Let's see a test case -
SQL> set serveroutput on
SQL> DECLARE
2 STV_FROM_DATE_BC VARCHAR2(20);
3 STV_FROM_DATEBCKUP DATE;
4 BEGIN
5 STV_FROM_DATEBCKUP:= NULL;
6 STV_FROM_DATE_BC :='01:JAN:2000:';
7 STV_FROM_DATE_BC :=STV_FROM_DATE_BC||'00:00:00';
8 dbms_output.put_line('Input date literal = '||STV_FROM_DATE_BC);
9 STV_FROM_DATEBCKUP:=to_date(STV_FROM_DATE_BC,'DD:MON:YYYY:HH24:MI:SS');
10 dbms_output.put_line('Date without format model = '||STV_FROM_DATEBCKUP);
11 dbms_output.put_line('Date with proper format model = '||TO_CHAR(STV_FROM_DATEBCKUP,'DD:MM:YYYY:HH24:MI:SS'));
12 END;
13 /
Input date literal = 01:JAN:2000:00:00:00
Date without format model = 01-JAN-00
Date with proper format model = 01:01:2000:00:00:00
PL/SQL procedure successfully completed.
SQL>

Select doesn't return expected result with Date type in Oracle DB

Table my_table is:
CREATE TABLE MY_TABLE(
ID NUMBER NOT NULL,
MY_DATE DATE NOT NULL);
Typing the following query:
select sysdate from dual
the result is:
10-MAG-12 21:22:32
note that mag = may.
Now, if I type this query:
select *
from my_table
where my_date <= sysdate
the result is:
9918 10-MAG-12 20:00:00
9915 10-MAG-12 21:00:00
9952 10-MAG-12 22:00:00
9951 10-MAG-12 23:00:00
Note that in my_table I have only these 4 records. Why I see all the records and not the first and second record only? Thanks.
I use Oracle SQL Developer.
Edit: please note that when I insert a record with PL/SQL I type something like:
nCount NUMBER;
myDate DATE;
stringDate VARCHAR2(255);
BEGIN
nCount := 0;
stringDate := substr(to_char(trunc(sysdate)),0,9);
myDate := to_date(stringDate || ' 20:00:00','dd-mm-yyyy hh24:mi:ss');
for t in (a cursor) loop
insert into MY_TABLE(ID,MY_DATE)
values (9918,myDate+(nCount/1440));
nCount := nCount + 60;
end loop;
END;
I suspect that the data being stored in your table does not have a year of 2012. It probably has a year of 0012 (two thousand years ago).
What do you see when you run the query
SELECT id, to_char( my_date, 'dd-mm-yyyy hh24:mi:ss' )
FROM my_table
I expect that the year will be 0012 rather than 2012. The reason for that is that the code you're using to insert the data is incorrectly converting a date to a string without using an explicit format mask then converts the string back to a date using an explicit format mask that happens not to match the session's NLS_DATE_FORMAT. In general, if you ever find yourself converting a date to a string and back to a date, you're probably doing something wrong. If you change your code to simply do date manipulation, it will be more efficient, more robust, and less error-prone.
DECLARE
nCount NUMBER;
myDate DATE;
BEGIN
nCount := 0;
myDate := trunc(sysdate) + interval '20' hour;
for t in (a cursor) loop
insert into MY_TABLE(ID,MY_DATE)
values (9918,myDate+(nCount/1440));
nCount := nCount + 60;
end loop;
END;
Walking through why the original code goes wrong
stringDate := substr(to_char(trunc(sysdate)),0,9);
This takes SYSDATE and truncates it to midnight on the current day. So far, so good. Then, it calls TO_CHAR without an explicit format mask. This causes Oracle to use the session's NLS_DATE_FORMAT, meaning that different users with different settings will get different results. If your session's NLS_DATE_FORMAT happens to be 'dd-mon-rr hh24:mi:ss', which I'm guessing based on the query results you posted, that will mean that the string has a 2-digit year. Your SUBSTR appears to assume that the output has just a two-digit year (if you have a different NLS_DATE_FORMAT, your SUBSTR will generate different bugs such as potentially cutting off the 12 from a year of 2012 leaving a year of just 20).
myDate := to_date(stringDate || ' 20:00:00','dd-mm-yyyy hh24:mi:ss');
Assuming stringDate is something like 10-MAG-12, this next line generates a string 10-MAG-12 20:00:00 and then tries to convert it to a date using the format mask dd-mm-yyyy hh24:mi:ss. This assumes that the string has a 4-digit year so when it only finds 2-digits, it assumes that you meant the year 12, not the year 2012.