Oracle Regular Expression for Date - sql

I have the following anonymous block. I've written a small regular expression to accept the date. It is able to validate the day, month, and year.
Now I want to consider the only century year, and regexp should also allow year in RR format (Century year). Also, I want to accept a day without a zero in the prefix; currently, it is accepting this for the month only. Please see the scenarios below:
> 01/12/2154 (Do not accept, It's not a century year)
> 11/12/1996 (Do not accept, It's not a century year)
> 11/2/24 (Accept, and a plsql logic in IF condition could change it to 2024; Add 20 to it)
> 1/9/2020 (Accept)
> 01/02/2020 (Accept)
> 1/29/20 (Accept)
> 01/11/25 (Accept)
> 1/09/2021 (Accept)
**
declare
date_v varchar2(40):= '01/01/2025';
date_n date;
begin
if regexp_like (date_v, '([0-9]|1[0-2])/([0-2][0-9]|3[0-1])/[0-9]{4}') then
-- logic
date_n:= TO_DATE(date_v,'MM/DD/YYYY');
dbms_output.put_line(date_n);
end if;
end;

You can do a regular expression on a date, but that is not a good way to check if a date is valid, it just checks if the format is ok. For example 30-FEB-2020 will pass your regex but that is not a valid date. The database can do the job for you instead. I usually use something like this:
FUNCTION is_valid_date (date_str_i VARCHAR2, format_i VARCHAR2) RETURN VARCHAR2
/* check if date is valid */
AS
l_dummy_dt DATE;
date_not_valid_for_m EXCEPTION;
PRAGMA EXCEPTION_INIT(date_not_valid_for_m, -01839);
BEGIN
l_dummy_dt := TO_DATE(date_str_i,format_i);
RETURN 'Y';
EXCEPTION WHEN date_not_valid_for_m THEN
RETURN 'N';
END;
Note that it only fails if the TO_DATE returns ora-01839: date not valid for month specified, you can easily add the other possible errors or just use a WHEN OTHERS THEN RETURN 'N';

Oracle is smart enough to recognize the string with or without 0 in date format.
So you can directly convert this string to TO_DATE('11/2/24', 'mm/dd/rrrr') and for recognizing the current century, You can use the TRUNC to century using the 'CC' as follows:
SQL> SELECT
2 TO_DATE('11/2/24', 'mm/dd/rrrr') YOUR_DATE,
3 CASE
4 WHEN TRUNC(TO_DATE('11/2/24', 'mm/dd/rrrr'), 'CC') = TRUNC(SYSDATE, 'CC') THEN
5 'ACCEPT'
6 ELSE
7 'REJECT'
8 END RESULT
9 FROM
10 DUAL;
YOUR_DATE RESULT
----------- ------
02-NOV-2024 ACCEPT
SQL>
Result of the query with another date from the past century:
SQL> SELECT
2 TO_DATE('11/12/1996', 'mm/dd/rrrr') YOUR_DATE,
3 CASE
4 WHEN TRUNC(TO_DATE('11/12/1996', 'mm/dd/rrrr'), 'CC') = TRUNC(SYSDATE, 'CC') THEN
5 'ACCEPT'
6 ELSE
7 'REJECT'
8 END RESULT
9 FROM
10 DUAL;
YOUR_DATE RESULT
----------- ------
12-NOV-1996 REJECT
SQL>
Note: I have used 'rrrr' for a year as it will convert any two-digit years to the year falling in 1950-2049.

Try this one:
with t(date_col) as (
select '01/01/2014' from dual
union all
select '1/2/2014' from dual
union all
select '01/3/2014' from dual
union all
select '1/04/2014' from dual
union all
select '11/1/14' from dual)
select date_col,
case
when regexp_instr(date_col, '^\d/\d/\d{4}$') = 1 then
'd/m/yyyy'
when regexp_instr(date_col, '^\d{2}/\d/\d{4}$') = 1 then
'dd/m/yyyy'
when regexp_instr(date_col, '^\d/\d{2}/\d{4}$') = 1 then
'd/mm/yyyy'
when regexp_instr(date_col, '^\d{2}/\d{2}/\d{4}$') = 1 then
'dd/mm/yyyy'
else
'Unknown format'
end date_format
from t;
DATE_COL DATE_FORMAT
---------- --------------
01/01/2014 dd/mm/yyyy
1/2/2014 d/m/yyyy
01/3/2014 dd/m/yyyy
1/04/2014 d/mm/yyyy
11/1/14 Unknown format
Refer to this link:
Regular expression on Dates in Oracle

Date validation with regular expression is difficult, very difficult. THr problem being that months have differing number of days, and 29-Feb is valid only in certain years. Your request is to have multiple valid formats greatly compounding.
The following function validates dates in the ISO-8601 format (YYYY-MM-DD), and only that format, but it does validate the correct number of days in each month for the dates 0001-01-01 through 9999-12-31, including leap days (29-Feb) when valid. but again just 1 format. Recommendation do not try validating dates with regular expression, esp multiple formats.
create or replace function valid_iso_date (iso_date_string varchar2)
return date
is
iso_date_pattern constant varchar2(256) :=
'^((0[0-9]{2}[1-9]|[1-9][0-9]{3})-((0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01])|02-(0[1-9]|1[0-9]|2[0-8])|(0[469]|11)-(0[1-9]|[12][0-9]|30))$)|^(\d\d(0[48]|[2468][048]|[13579][26])-02-29$)';
result_date date := null;
begin
if regexp_like(iso_date_string, iso_data_pattern)
then
result_date := to_date(iso_date_string,'yyyy-mm-dd');
end if;
return result_date;
end valid_iso_date;

Related

ORA-01841: (full) year must be between -4713 and +9999, and not be 0 tips

I would like to group all values of my Column DATUM of the Table test_tbl which are greater than 01.01.2020 . When I run the query:
SELECT to_date("DATUM", 'YYYYMM')
FROM test_tbl
WHERE to_date("DATUM", 'YYYYMM') >= to_date('2020-01-01' ,'YYYY-MM-DD')
GROUP BY to_date("DATUM", 'YYYYMM')
I get the following Error
ORA-01841: (full) year must be between -4713 and +9999, and not be 0.
The table test_tbl looks like:
DATUM (varchar2)
201701
202001
201901
201801
202003
When I run only the GROUP BY without the WHERE CLAUSE the date convertation works and there are no NULL values or somithing similiar
SELECT to_date("DATUM", 'YYYYMM')
FROM test_tbl
GROUP BY to_date("DATUM", 'YYYYMM')
`
This is a data error. Somewhere in your DATUM column you have a string which cannot be cast to a date. This is always a risk when we store data as the wrong datatype.
If you are on Oracle 12c R2 or higher you can easily locate the errant row(s) with a query like this:
select * from your_table
where validate_conversion(datum as date, 'yyyymm') = 0
If you are on an earlier version of the database you can create a function which does something similar....
create or replace function is_date(p_str in varchar2
,p_mask in varchar2 := 'yyyymm' ) return number is
n pls_integer;
begin
declare
dt date;
begin
dt := to_date(p_str, p_mask);
n := 1;
exception
when others then
n := 0;
end;
return n;
end;
/
Like validate_conversion() this returns 1 for valid dates and 0 for invalid ones.
select * from your_table
where is_date(datum, 'yyyymm') = 0
This approach is safer because it applies Oracle's actual date verification mechanism. Whereas, using pattern matching regexes etc leaves us open to weak patterns which pass strings which can't be cast to dates.
Your datum column would need to contain only numeric data in order to be processed as a date in the format you specified. Plus the last two digits would need to be between 01 and 12; but that's a separate issue since your error message is complaining about the value for year.
So you could check for invalid values with:
SELECT
datum
,LENGTH(REGEXP_REPLACE(datum,'[0-9]','')) as char_count
FROM test_tbl
WHERE LENGTH(REGEXP_REPLACE(datum,'[0-9]','')) > 0
;
Should spin through 300k rows fairly quickly.

Oracle query to get min or max value of a datatype

Does Oracle have a function or query that will return the min or max value of a datatype? From the documentation I know that the minimum value of a date field is January 1, 4712 BCE. Is there anyway to get that date value from a select statement?
Select ?something? from dual;
If you write a little piece of code, then then answer is yes. Not exactly a single SELECT (unless you put all that into a function), though.
SQL> set serveroutput on;
SQL> declare
2 datum date := trunc(sysdate);
3 begin
4 while 1 <> 2 loop
5 datum := datum - 1;
6 end loop;
7 exception
8 when others then
9 dbms_output.put_line('Last valid date = ' ||
10 to_char(datum, 'dd.mm.yyyy bc'));
11 end;
12 /
Last valid date = 01.01.4712 BC
PL/SQL procedure successfully completed.
SQL>
That was quick (I mean, code takes no time to execute). For other datatypes it isn't that fast (such as which is the smallest valid integer?), at least not by using the same principle.
Use first value in julian calendar
select to_date(1, 'j') from dual
displayed results depends on server and client settings, but it is a date 4712-01-01 BC.
dbfiddle demo

Oracle - Format return of sysdate inside select

I did a select in oracle database to return the time that ticket is in a support group.
Sometimes, I have the scenario where we have the date of ticket joined the group, but I'm not out of time.
To workaround this problem, I put in my select a condition and worked with diff between the date of entry in the group and the sysdate.
The problem is the output that is being formatted as follows: +00 00:28:32.00000, and I need only the time in minutes.
Below, I added the whole query, I suppose that the problem is in this part:
CASE
WHEN PBTI_TEMPONOGRUPO IS NULL
THEN (SYSDATE-(SECS_TO_DATE(PBTI_DATAENTRADAGRUPO)))
END AS TEMPO_NO_GRUPO
How to format this output?
The query is:
SELECT PBTI_WORKORDER_ID AS ID_WO,
PBTI_IDREQUISICAO AS ID_REQ,
PBTI_GRUPOSUPORTEATUAL AS GRUPO_SUP_ATUAL,
PBTI_GRUPOSUPORTEANTERIOR AS GRUPO_SUP_ANTERIOR,
CASE
WHEN PBTI_DATASAIDAGRUPO IS NULL
THEN SYSDATE
WHEN PBTI_DATASAIDAGRUPO IS NOT NULL
THEN SECS_TO_DATE(PBTI_DATASAIDAGRUPO)
END AS DATA_SAIDA_GRUPO,
SECS_TO_DATE(PBTI_DATAENTRADAGRUPO) AS DATA_ENTRADA,
CASE
WHEN PBTI_TEMPONOGRUPO IS NULL
THEN (SYSDATE-(SECS_TO_DATE(PBTI_DATAENTRADAGRUPO)))
END AS TEMPO_NO_GRUPO
FROM PBTI_TABELA_INDICADORES
WHERE PBTI_WORKORDER_ID = 'WO0000000142585';
CASE
WHEN PBTI_DATASAIDAGRUPO IS NULL
THEN SYSDATE
WHEN PBTI_DATASAIDAGRUPO IS NOT NULL
THEN SECS_TO_DATE(PBTI_DATASAIDAGRUPO)
END
So the output of the CASE expressions will be DATE data type. To get the output in your desired format, use TO_CHAR along with desired FORMAT MODEL to convert it into a string.
For example,
SQL> SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD') only_date FROM DUAL;
ONLY_DATE
----------
2015-10-20
SQL> SELECT TO_CHAR(SYSDATE, 'HH24:MI:SS') only_time FROM DUAL;
ONLY_TIME
---------
20:29:54

All rows with date <= 90 days in oracle based on varchar2 date string [duplicate]

I have the following query that I am attempting to use as a COMMAND in a crystal report that I am working on.
SELECT * FROM myTable
WHERE to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
This works fine, however my only concern is that the date may not always be in the correct format (due to user error). I know that when the to_date function fails it throws an exception.. is it possible to handle this exception in such a way that it ignores the corresponding row in my SELECT statement? Because otherwise my report would break if only one date in the entire database is incorrectly formatted.
I looked to see if Oracle offers an isDate function, but it seems like you are supposed to just handle the exception. Any help would be greatly appreciated. Thanks!!
Echoing Tony's comment, you'd be far better off storing dates in DATE columns rather than forcing a front-end query tool to find and handle these exceptions.
If you're stuck with an incorrect data model, however, the simplest option in earlier versions is to create a function that does the conversion and handles the error,
CREATE OR REPLACE FUNCTION my_to_date( p_date_str IN VARCHAR2,
p_format_mask IN VARCHAR2 )
RETURN DATE
IS
l_date DATE;
BEGIN
l_date := to_date( p_date_str, p_format_mask );
RETURN l_date;
EXCEPTION
WHEN others THEN
RETURN null;
END my_to_date;
Your query would then become
SELECT *
FROM myTable
WHERE my_to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
Of course, you'd most likely want a function-based index on the MY_TO_DATE call in order to make this query reasonably efficient.
In 12.2, Oracle has added extensions to the to_date and cast functions to handle conversions that error
SELECT *
FROM myTable
WHERE to_date(myTable.sdate default null on conversion error, 'MM/dd/yyyy') <= {?EndDate}
You could also use the validate_conversion function if you're looking for all the rows that are (or are not) valid dates.
SELECT *
FROM myTable
WHERE validate_conversion( myTable.sdate as date, 'MM/DD/YYYY' ) = 1
If your data is not consistent and dates stored as strings may not be valid then you have 3 options.
Refactor your DB to make sure that the column stores a date datatype
Handle the exception of string to date in a stored procedure
Handle the exception of string to date in a (complex) record selection formula
I would suggest using the first option as your data should be consistent.
The second option will provide some flexibility and speed as the report will only fetch the rows that are needed.
The third option will force the report to fetch every record in the table and then have the report filter down the records.
I have the same problem... an old legacy database with varchar fields for dates and decades of bad data in the field. As much as I'd like to, I can't change the datatypes either. But I came up with this solution to find if a date is current, which seems to be what you're doing as well:
select * from MyTable
where regexp_like(sdate, '[0-1][0-9].[0-3][0-9].[0-9][0-9][0-9][0-9]')
-- make sure it's in the right format and ignore rows that are not
and substr(sdate,7,10) || substr(sdate,1,2) || substr(sdate,4,5) >= to_char({?EndDate}, 'YYYYMMDD')
-- put the date in ISO format and do a string compare
The benefit of this approach is it doesn't choke on dates like "February 30".
Starting from Oracle 12c there is no need to define a function to catch the conversion exception.
Oracle introduced an ON CONVERSION ERROR clause in the TO_DATE function.
Basically the clause suppress the error in converting of an invalid date string (typical errors are ORA-01843, ORA-01841, ORA-011861, ORA-01840) and returns a specified default value or null.
Example of usage
select to_date('2020-99-01','yyyy-mm-dd') from dual;
-- ORA-01843: not a valid month
select to_date('2020-99-01' default null on conversion error,'yyyy-mm-dd') from dual;
-- returns NULL
select to_date('2020-99-01' default '2020-01-01' on conversion error,'yyyy-mm-dd') from dual;
-- 01.01.2020 00:00:00
Solution for the Legacy Application
Let's assume there is a table with a date column stored as VARCHAR2(10)
select * from tab;
DATE_CHAR
----------
2021-01-01
2021-99-01
Using the above feature a VIRTUAL DATE column is defined, that either shows the DATE or NULL in case of the conversion error
alter table tab add (
date_d DATE as (to_date(date_char default null on conversion error,'yyyy-mm-dd')) VIRTUAL
);
select * from tab;
DATE_CHAR DATE_D
---------- -------------------
2021-01-01 01.01.2021 00:00:00
2021-99-01
The VIRTUAL column can be safely used because its format is DATE and if required an INDEX can be set up on it.
select * from tab where date_d = date'2021-01-01';
Since you say that you have "no access" to the database, I am assuming that you can not create any functions to help you with this and that you can only run queries?
If that is the case, then the following code should get you most of what you need with the following caveats:
1) The stored date format that you want to evaluate is 'mm/dd/yyyy'. If this is not the case, then you can alter the code to fit your format.
2) The database does not contain invalid dates such as Feb 30th.
First, I created my test table and test data:
create table test ( x number, sdate varchar2(20));
insert into test values (1, null);
insert into test values (2, '01/01/1999');
insert into test values (3, '1999/01/01');
insert into test values (4, '01-01-1999');
insert into test values (5, '01/01-1999');
insert into test values (6, '01-01/1999');
insert into test values (7, '12/31/1999');
insert into test values (8, '31/12/1999');
commit;
Now, the query:
WITH dates AS (
SELECT x
, sdate
, substr(sdate,1,2) as mm
, substr(sdate,4,2) as dd
, substr(sdate,7,4) as yyyy
FROM test
WHERE ( substr(sdate,1,2) IS NOT NAN -- make sure the first 2 characters are digits
AND to_number(substr(sdate,1,2)) between 1 and 12 -- and are between 0 and 12
AND substr(sdate,3,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,4,2) IS NOT NAN -- make sure the next 2 are digits
AND to_number(substr(sdate,4,2)) between 1 and 31 -- and are between 0 and 31
AND substr(sdate,6,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,7,4) IS NOT NAN -- make sure the next 4 are digits
AND to_number(substr(sdate,7,4)) between 1 and 9999 -- and are between 1 and 9999
)
)
SELECT x, sdate
FROM dates
WHERE to_date(mm||'/'||dd||'/'||yyyy,'mm/dd/yyyy') <= to_date('08/01/1999','mm/dd/yyyy');
And my results:
X SDATE
- ----------
2 01/01/1999
The WITH statement will do most of the validating to make sure that the sdate values are at least in the proper format. I had to break out each time unit month / day / year to do the to_date evaluation because I was still getting an invalid month error when I did a to_date on sdate.
I hope this helps.
Trust this reply clarifies...
there is no direct EXCEPTION HANDLER for invalid date.
One easy way is given below once you know the format like DD/MM/YYYY then below given REGEXP_LIKE function will work like a charm.
to_date() also will work, when invalid_date is found then cursor will goto OTHERS EXCEPTION. given below.
DECLARE
tmpnum NUMBER; -- (1=true; 0 = false)
ov_errmsg LONG;
tmpdate DATE;
lv_date VARCHAR2 (15);
BEGIN
lv_date := '6/2/2018'; -- this will fail in *regexp_like* itself
lv_date := '06/22/2018'; -- this will fail in *to_date* and will be caught in *exception WHEN OTHERS* block
lv_date := '07/03/2018'; -- this will succeed
BEGIN
tmpnum := REGEXP_LIKE (lv_date, '[0-9]{2}/[0-9]{2}/[0-9]{4}');
IF tmpnum = 0
THEN -- (1=true; 0 = false)
ov_errmsg := '1. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg);
RETURN;
END IF;
tmpdate := TO_DATE (lv_date, 'DD/MM/RRRR');
--tmpdate := TRUNC (NVL (to_date(lv_date,'DD/MM/RRRR'), SYSDATE));
tmpnum := 1;
EXCEPTION
WHEN OTHERS
THEN
BEGIN
tmpnum := 0;
ov_errmsg := '2. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg || SQLERRM);
RETURN;
END;
-- continue with your other query blocks
END;
-- continue with your other query blocks
DBMS_OUTPUT.PUT_LINE (tmpnum);
END;

Oracle convert RAW to date format

I have a RAW field in my Oracle database that represents the date of user registered in system.
The value is something like 24E2321A0000000000 However I need convert the value to the date it represents (etc 2008-12-25 15:04:31).
I tried with totimestamp (see this sqlfiddle) but that didn't work.
Maybe this will help:
SELECT utl_raw.cast_to_binary_integer('24E2321A0000000000') raw_to_int
FROM dual
/
Output is 36. I'm not sure if you need days or hours. Next example is about adding 36 hours to SYSDATE:
-- SYSDATE + 36/24 --
SELECT SYSDATE+(utl_raw.cast_to_binary_integer('24E2321A0000000000')/24) my_date
FROM dual
/
MY_DATE
---------------------
12/13/2013 4:29:22 AM
please try one
declare
d date;
begin
dbms_stats.convert_raw_value (hextoraw('7876070A010101'), d);
dbms_output.put_line (d);
end;