Confusion on using variables & constants in PL/SQL - oracle

I've new to PL/SQL (and it's been a while since I've used vanilla SQL). I've got a query that I inherited that I'm trying to schedule in TOAD. In order for that to work I have to change hard coded date references to be calculated at run time.
To that end I added a Declare statement to the front of the query, added the necessary constants, setting them at declaration, and then had the query use them.
When I try to execute an error gets thrown saying a Select Into. To my understanding, SELECT Into is used to set a variable based on a value in the db (based on Constants in Oracle SQL query), whereas I'm looking to define the value independent of any value in the db (in this case the date on the server). The full error follows:
ORA-06550: line 6, column 5:
PLS-00428: an INTO clause is expected in the Select statement
So I'm looking for a little guidance on where my understanding of variables/constants in PL/SQL is off, and also help with getting the following to execute:
DECLARE OLD CONSTANT char(11):= to_Char(SYSDATE - 6, 'DD-MON-YYYY');
NEW CONSTANT char(11):= to_char(SYSDATE, 'DD-MON-YYYY');
BEGIN
SELECT CASE
WHEN (userhost LIKE 'a%'
AND userid IN ('s',
'sub')) THEN 'BATCH'
WHEN userid LIKE 'N%' THEN 'N'
WHEN ((userhost LIKE 'b%'
OR userhost LIKE 'c%')
AND userid IN ('s',
'sub')) THEN 'Forms'
WHEN ((userid LIKE '%_IU%'
OR userid LIKE 'RPT%'
OR userid IN ('q',
'r',
'p'))
AND userhost <> 'n%') THEN 'Interface'
ELSE 'Other'
END app_type , round(sum(sessioncpu/100), 1) cpu_seconds , (sum(sessioncpu/100) / (119*1*60*60) * 100) pct_of_cpu,
trunc(ntimestamp#,'MI')
FROM PERFSTAT.AUD$_ARCHIVE
WHERE ntimestamp# BETWEEN to_timestamp(OLD || ' 23:59','DD-MON-YYYY HH24:MI') AND to_timestamp(NEW || ' 00:00','DD-MON-YYYY HH24:MI')
AND logoff$time < to_date(NEW || ' 00:00','DD-MON-YYYY HH24:MI')
GROUP BY CASE
WHEN (userhost LIKE 'a%'
AND userid IN ('s',
'sub')) THEN 'BATCH'
WHEN userid LIKE 'N%' THEN 'N'
WHEN ((userhost LIKE 'b%'
OR userhost LIKE 'c%')
AND userid IN ('s',
'sub')) THEN 'Forms'
WHEN ((userid LIKE '%_IU%'
OR userid LIKE 'RPT%'
OR userid IN ('q',
'r',
'p'))
AND userhost <> 'n%') THEN 'Interface'
ELSE 'Other'
END app_type,
trunc(ntimestamp#,'MI')
ORDER BY trunc(ntimestamp#,'MI'),
1;
END;

You have two issues here. The first is trying to use the CHAR datatype and then not giving it a length. This defaults to a CHAR(1), i.e. a single character. For memory concerns, you might also consider VARCHAR2 instead. https://docs.oracle.com/cd/E17952_01/refman-5.1-en/char.html
The second issue has to do with the INTO clause as mentioned in your question. When you run a SELECT statement in PL/SQL (not associated to DML), you have to give Oracle something to return the result set into. You can then use those variables, whether printing them, storing them, or doing processing with them.

I'd have to see the error, but I think that it may want you to set a length to your char. So, something like char(30).
Also, I'm a big fan of varchar2. Only uses as much space in the DB as the characters in the variable. So, it it's varchar2(500) and has 8 characters, it only uses 8 chars worth of memory.

Your query has an inherent flaw, in that anything that occurs between 23:59 and 0:00 will satisfy conditions at both ends of the range (e.g. something that happens at 23:59:30). If this query were my responsibility, I'd get rid of the variables and the text conversions altogether:
WHERE ntimestamp# >= TRUNC (SYSDATE) - 6
AND ntimestamp# < TRUNC (SYSDATE)
AND logoff$time < TRUNC (SYSDATE)
Using >= and < for dates where you want to avoid an overlap tends to be safer that using between.
Taking a closer look, I'm not sure what the point of your query using one minute before midnight on the lower bound is. That kind of thing is more typically done on the upper bound. Assuming that you're actually doing that for a reason, you can still get around transforming to a string by using either of the following:
WHERE ntimestamp# BETWEEN TRUNC (SYSDATE) - 6 - (1 / 24 / 60)
AND TRUNC (SYSDATE)
AND logoff$time < TRUNC (SYSDATE)
WHERE ntimestamp# BETWEEN TRUNC (SYSDATE)
- NUMTODSINTERVAL (6, 'DAY')
- NUMTODSINTERVAL (1, 'MINUTE')
AND TRUNC (SYSDATE)
AND logoff$time < TRUNC (SYSDATE)
All of that is really just an aside to your main problem though: you need to tell the interpreter what to do with the result of the query. That means that you need to provide a variable to put the result in, then (presumably) do something with the result. One way to do this is to use a cursor loop:
DECLARE
CURSOR cur_query IS
[your query goes here];
BEGIN
FOR r_query IN cur_query LOOP
DBMS_OUTPUT.put_line (r_query.app_type);
DBMS_OUTPUT.put_line (r_query.cpu_seconds);
DBMS_OUTPUT.put_line (r_query.pct_of_cpu);
END LOOP;
END;
Of course, the alternative is just to run your query as SQL, rather than PL/SQL. With the variables eliminated, that will be easier.
Comment Response
PL/SQL blocks are not intended to return query results, like you get if you run straight SQL in Toad. There are ways to fake it via functions that return user-defined types or pipelined functions, but you're better off writing SQL if you are able to (and, in this case, you should be able to).
I'm not sure what you mean by "the variables are supposed to dynamically set the date range to look at". The code provided is returning data relative to sysdate, not getting outside data. You can do that in the query as easily as you can in a PL/SQL block.

Related

comparar fechas de diferentes tablas al detalle de los segundos

Good evening,
I have a SP and I want to compare 2 dates from different tables, but in the form 'dd/mm/yyyy hh:mi:ss'
I am using to_char(date01,'dd/mm/yyyy hh:mi:ss')> to_char(date02,'dd/mm/yyyy hh:mi:ss')
but it throws me errors.
For example: if the date is 02/12/2016 07:40:12>02/02/2022 06:40:46
it indicates that it is true, and it is not, it is considering the day and not the entire date.
when I only use date01>date02, I have the problem you consider for example.
'02/15/2022 07:48:50'='02/15/2022 07:50:22' (only considers the date)
How can I compare date, minutes and seconds regardless of the server configuration.
Thank you,
PROCEDURE SPU_CUENTA
(
p_nro in varchar2,
pr_Ret OUT number
) is
vfecha varchar(100);
vcount int;
begin
select COUNT(DFEC_SISTEMA) into vcount from TAB Where c=1;
IF vcount>0 THEN
select to_char(DFEC_SISTEMA,'dd/mm/yyyy hh:mi:ss') into vfecha from TAB Where c=1;
EXECUTE IMMEDIATE 'SELECT COUNT(DFEC_ANULA) FROM tablab WHERE to_char(DFEC_ANULA,'dd/mm/yyyy hh:mi:ss')>'''||vfecha||'''' into pr_Ret;
END IF;
end;
Code you suggest would make sense if columns involved were VARCHAR2 (which is a bad idea; store dates into DATE datatype columns).
If those columns really are DATEs, then part of your question (which suggests format) is meaningless - we compare dates as they are, simply by e.g. date1 > date2. Converting them to characters - in a format you specified - is plain wrong.
If those columns are strings, then you'll have to convert them TO_DATE, not TO_CHAR
Procedure you wrote should be a function; they are designed to return a value. Yes, you can use a procedure, but - why would you? You can't use it in SQL (only in PL/SQL).
Besides, code can be heavily shortened/optimized, as you need just one select statement. You don't have to first check whether there any rows in tab that satisfy the condition, and then select some other info - use a subquery instead.
Finally, why are you using dynamic SQL? There's nothing dynamic in your code.
I'd suggest something like this, see if it makes sense.
FUNCTION spu_cuenta (p_nro IN VARCHAR2)
RETURN NUMBER
IS
pr_ret NUMBER;
BEGIN
SELECT COUNT (dfec_anula)
INTO pr_ret
FROM tablab
WHERE dfec_anula > (SELECT dfec_sistema
FROM tab
WHERE c = 1);
RETURN pr_ret;
END;

How to retrieve currency amount as words using oracle sql

In the below code, I am trying to get the amount in words for the value in the AMOUNT column but can't seem to get it right. Anyone with an idea? Below the CREATE VIEW statement:
SELECT GLR3 AS RECEIPT_DOC_NO,
GLANI AS ACCOUNT_NUMBER,
GLSBL AS JDE_NO,
(SELECT YAALPH FROM PRODDTA.F060116 WHERE YAAN8 = T1.GLSBL) AS STAFF_NAME,
CASE GLDGJ WHEN 0 THEN TO_DATE (TO_CHAR (1 + 1900000), 'YYYYDDD')
ELSE TO_DATE (TO_CHAR (GLDGJ + 1900000), 'YYYYDDD') END AS GL_DATE,
GLEXA AS NARRATIVE,
GLLT AS LEDGER_TYPE,
GLSBLT AS SUBLEDGER_TYPE,
GLCRCD AS CURRENCY_CODE,
CASE GLAA WHEN 100 THEN ROUND (GLAA / 100, 2) ELSE ROUND (GLAA / 100, 2)
END AS AMOUNT,
(SELECT TO_CHAR(TO_DATE(T1.GLAA,'J'),'JSP')) FROM DUAL) AS AMOUNT_INWORDS
FROM PRODDTA.F0911 T1;
My code is failing with
ORA-00923: FROM keyword not found where expected.
ORA-00923: FROM keyword not found where expected.
Indicates a syntax error. In this case you have three ( and four ):
(SELECT TO_CHAR(TO_DATE(T1.GLAA,'J'),'JSP')) FROM DUAL) AS AMOUNT_INWORDS
The compiler is not expecting the second ) after 'JSP'.
The scalar cursor is unnecessary, so the simplified and corrected version would be:
TO_CHAR(TO_DATE(T1.GLAA,'J'),'JSP') AS AMOUNT_INWORDS
ORA-01854: julian date must be between 1 and 5373484
5373484 is the Julian date for 31-DEC-9999 which is the maximum date allowed in Oracle. This poses an absolute limit on the number which we can spell using JSP mask ... in one go. However, we can use PL/SQL to break larger numbers into chunks and spell those instead. The inestimable Tom Kyte wrote such a function and published it on the AskTom site. Find it here.
I think the value i'm trying to write in words has decimal values (400.00) hence the ORA-01854 error.
The Tom Kyte I linked to does address that issue further down the page. It's this comment here.
Data retrieved in amount column is preceded by a negative symbol when it shouldn't be,
If you simply want to ignore negative values then apply the ABS() function to give you the absolute value.

Oracle. CAST COLLECT for date datatype

Consider the types:
CREATE OR REPLACE TYPE date_array AS TABLE OF DATE;
CREATE OR REPLACE TYPE number_array AS TABLE OF NUMBER;
CREATE OR REPLACE TYPE char_array AS TABLE OF VARCHAR2(80);
Queries:
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(ID) AS number_array)
FROM q;
return collection of numbers
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(STRING) AS char_array)
FROM q;
return collection of strings
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
return error invalid datatype.
Can anyone explain why the Date datatype behaves differently?
Here are my findings... it seems you are facing a bug caused by the fact that calculated dates seem to have a different internal representation from "database" dates. I did find a workaround, so keep on reading.
On my oracle dev installation (Oracle 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production) I am experiencing your same problem.
BUT... If I create a physical table containing your test data:
create table test_data as
SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5
and then run the "cast collect" operator on this physical table, it works as expected:
-- this one works perfectly
SELECT CAST(COLLECT(MyDate) AS date_array) from test_data
but all these examples still do NOT work:
-- here I just added 1 .. and it doesn't work
SELECT CAST(COLLECT(MyDate + 1) AS date_array)
from test_data
-- here I am extracting sysdate, instead of a physical column... and it doesn't work
SELECT CAST(COLLECT(sysdate) AS date_array)
from test_data
It feels like oracle doesn't think that calculated dates are the same thing of physical dates
So, I tried to "persuade" oracle that the data I am providing is actually a normal DATE value, using an explicit cast... and EUREKA! this does the job correctly:
WITH q AS
(SELECT LEVEL ID,
-- this apparently unnecessary cast does the trick
CAST( TRUNC(SYSDATE) + LEVEL AS DATE) MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
Yes... but why??
It really seems that these two values are not exactly the same thing, even if the values we see are actually the same:
select sysdate, cast (sysdate as date) from dual
So I dug in the internal representation of the two values applying the "dump" function to both of them:
select dump(sysdate), dump(cast (sysdate as date)) from dual
and these are the results I got:
DUMP(SYSDATE ) -> Typ=13 Len=8: 226,7,11,9,19,20,47,0
DUMP(CAST(SYSDATEASDATE) as DUAL) -> Typ=12 Len=7: 120,118,11,9,20,21,48
Internally they look like two totally different data types! one is type 12 and the other is type 13... and they have different length and representation.
Anyway I discovered something more.. it seems someone else has noticed this: https://community.oracle.com/thread/4122627
The question has an answer pointing to this document: http://psoug.org/reference/datatypes.html
which contains a lengthy note about dates... an excerpt of it reads:
"What happened? Is the information above incorrect or does the DUMP()
function not handle DATE values? No, you have to look at the "Typ="
values to understand why we are seeing these results. ". The datatype
returned is 13 and not 12, the external DATE datatype. This occurs
because we rely on the TO_DATE function! External datatype 13 is an
internal c-structure whose length varies depending on how the
c-compiler represents the structure. Note that the "Len=" value is 8
and not 7. Type 13 is not a part of the published 3GL interfaces for
Oracle and is used for date calculations mainly within PL/SQL
operations. Note that the same result can be seen when DUMPing the
value SYSDATE."
Anyway, I repeat: I think this is a bug, but at least I found a workaround: use an explicit cast to DATE.

compare 13digit (millisecond) unix timestamp with date in oracle

A database column (VARCHAR2 datatype) stores the date/time as 13 digit (milliseconds
) unixtimestamp format. Now when I want to compare the column with a oracle date (in question), The error thrown as 'invalid number'
I tried both ways,
converting the 13digit number to Date and compare with the date in question like below. The expressions seems valid as they are printed in select query, but if i include in the where part, it throws 'invalid number'
Here 'value' is 13th digit unixtimestamp column of VARCHAR2 datatype.
select
TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000,
TO_DATE('2014-04-21', 'YYYY-MM-DD')
from dummytable
-- where and TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000 > TO_DATE('2014-04-21', 'YYYY-MM-DD')
converting the date in question to 13digit unixtimestamp and comparing with the database column.The expressions seems valid as they are printed in select query, but if i include in the where part, it throws 'invalid number'
.
select
value,
(to_date('2013-04-21', 'YYYY-MM-DD') - to_date('1970-01-01', 'YYYY-MM-DD')) * (1000*24*60*60)
from dummytable
-- where value > ((to_date('2013-04-21', 'YYYY-MM-DD') - to_date('1970-01-01', 'YYYY-MM-DD')) * (1000*24*60*60))
any pointers? Thanks in advance.
[EDIT- 1 day later] I see the problem now. There are some data (other rows) for the 'value' column that are non-numeric. But I have another column say field, where always field='date' return value as 13 digit timestamp. Now I think when 'where' condition executes, although the field='date' is in the condition, it is still validating the other values for 'value' which are non-numeric. Is there a way to avoid this ?
Your code works just fine. The problem is in your data. Some of your values is not a number.
create table test
(value varchar2(13));
insert into test(value) values('2154534689000');
--insert into test(value) values('2 54534689000');
select TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000
from test
where TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000 > TO_DATE('2014-04-21', 'YYYY-MM-DD');
This code works fine. But if you uncommented the second insert, you would get exactly the same invalid number error as you get.
UPD. Allan gave you a nice hint, but i feel that it can be good to explain you a bit about views. The fact that you select from a view CAN make a difference. A view is not stored somewhere physically, when you select from it, it is just "added to your query". And then Oracle Query Optimizer starts working. Among other things, it can change the order in which your where predicates are evaluated.
For example, your the view query can have a line where value is not null and it would normally show only 'good' values. But if your query has a predicate where to_date(value,'ddmmyyyy') > sysdate, Oracle can decide to evaluate your predicate earlier, because Oracle predicts that it would "cut off" more rows, thus making the whole query faster and less momery consuming. Of course, execution will crash because of an attempt to convert a null string to date.
I believe, that Allan in his answer that he gave a link to, gave a great way to solve this problem: "wrapping" your query in a subquery that Oracle can't "unwrap":
select value
from
(select value
from my_view
where rownum > 0)
where to_date(value,'ddmmyyyy') > sysdate
Hope that helps.

How to handle to_date exceptions in a SELECT statment to ignore those rows?

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;

Resources