Oracle Reports Comparing Database Timestamp to Current Timestamp - oracle

I'm attempting to write a BeforeReport Trigger that checks to see if a user is accessing report data within 120 seconds of creation.
My code is as follows:
function BeforeReport return boolean is
ENTRYDATE TIMESTAMP;
SERVERDATE TIMESTAMP;
DIFFSECONDS NUMBER;
begin
/*Checks to see if user is accessing the data within 120 seconds of creation in database*/
VALIDTIMELENGTH := 120;
SELECT SYS_DATE INTO ENTRYDATE FROM WOS_REPORT_PARAM WHERE SEQUENCE_NUM=UPPER(:SEQUENCENUM) AND PARAMETER='year';
SERVERDATE := CURRENT_TIMESTAMP;
DIFFSECONDS := ((EXTRACT(YEAR FROM ENTRYDATE)- EXTRACT(YEAR FROM SERVERDATE))*31536000) +
((EXTRACT(MONTH FROM ENTRYDATE)- EXTRACT(MONTH FROM SERVERDATE))*2592000) +
((EXTRACT(DAY FROM ENTRYDATE) - EXTRACT(DAY FROM SERVERDATE))*86400) +
((EXTRACT(HOUR FROM ENTRYDATE) - EXTRACT(HOUR FROM SERVERDATE))*3600) +
((EXTRACT(MINUTE FROM ENTRYDATE) - EXTRACT(MINUTE FROM SERVERDATE))*60) +
(EXTRACT(SECOND FROM ENTRYDATE) - EXTRACT(SECOND FROM SERVERDATE));
IF DIFFSECONDS > VALIDTIMELENGTH THEN RETURN(FALSE);
ELSE
return (TRUE);
END IF;
END;
The problem is, my code seems to return true regardless of how much time has gone by. Am I not implementing my seconds/minutes code correctly? Oddly enough , I experimented with making both returns (FALSE) and it still proceeded to pass the report! Oracle Reports is so inconsistent that it's infuriating. Any response is greatly appreciated.

If wos_report_param.sys_date is a TIMESTAMP (note that it's rather confusing to name a column sys_date when there is a sysdate function that returns a DATE), and assuming that wos_report_param.sys_date is a timestamp in the past, your logic can be simplified quite a bit
IF( current_timestamp - entrydate > numtodsinterval( validTimeLength, 'second' ) )
THEN
RETURN true;
ELSE
RETURN false;
END IF;
If my guess is correct and wos_report_param.sys_date is a timestamp in the past, then the diffseconds you are computing will be a negative number which explains why your procedure always returns false. You need to subtract the earlier date from the later date in order to yield a positive rather than a negative interval. Or you could throw an abs around your diffseconds calculation.

Related

'Different results for the same function called from SQL Developer UI and in SQL query

F is a function counting the number of weekdays betwwen two dates (using the format to_date(a, 'DD/MM/YYYY') [NLS_DATE_LANGUAGE = "FRENCH"].
It is stored in package P.
With a is the beginning date of the period and b the end.
When I use the SQL developer to run the function i.e. Right click on package p, execute, choose f function and fill the arguments:
if a = to_date('02/08/2019', 'DD/MM/YYYY') and b = to_date('12/08/2019', 'DD/MM/YYYY'))
I get a number of days = 7, which is the correct result.
But when I run it in SQL:
select p.f('02/08/2019','12/08/2019') from DUAL
I get a number of days = 8 and this result is obviously wrong.
I cannot understand how these two process can return two different values since the same F function is called, and the arguments are exactly the same.
I have been careful about the date format, still the two ways of calling f won't return the same result.
For information, this is the content of this function (sorry, cariable are expressed in french)
FUNCTION getjoursouvres
(
i_debut IN DATE,
i_fin IN DATE
)
RETURN NUMBER IS o_result NUMBER;
v_jour date;
v_nbjours NUMBER;
v_testferie number;
v_testweekend number;
v_testglobal number;
-- This cursor browses all dates between i_debut and i_fin (included)
cursor cx is
select to_date(i_debut, 'DD/MM/YYYY') + rownum -1 dt
from dual
connect by level <= to_date(i_fin, 'DD/MM/YYYY') - to_date(i_debut, 'DD/MM/YYYY') + 1;
BEGIN
open cx;
v_nbjours := 0;
loop
-- Browses all the days in the interval (begining and end included)
fetch cx into v_jour;
exit when cx%NOTFOUND;
-- testferie return 1 if the day is NOT a holiday, 0 if it is (so not be be added)
v_testferie := testferie(v_jour);
-- testweekend return 1 if the day is a weekday, 0 if it is on weekend (so not be be
v_testweekend := testweekend(v_jour);
--
v_testglobal := v_testferie + v_testweekend;
-- If v_testglobal = 2 then the day is neither weekend nor holiday. Therefore it is aded to the sum of days
if v_test = 2 then
v_nbjours := v_nbjours + 1;
end if;
end loop;
o_result := v_nbjours;
close cx;
return o_result;
END;
After many calls of both way to use the function. I reach to the conclusion that the element inducing the anomaly comes from the ue of
to_date(v_date, 'DD/MM/YYYY')
where v_date is already declared as a date format behave in an inpredictable way.
I decided to keep the date variable as such, and only use to_date for different cariable format.
I am not able to explain why exactly, but it solved my problems concerning dates behavior. Now I can call a function with data parameter from DUAL or from the SQL developer interface and get identical and coherent results.

How to form oracle function - if /else return different TIMESTAMP values

I am trying to create a sql function that returns one TIMESTAMP value if a certain condition is met, or else return a TIMESTAMP value plus an extra 30 days. Here is my function as created (but not compiling):
CREATE OR REPLACE FUNCTION "DUEDATE" (DATE_A IN TIMESTAMP, NUM_DAYS IN NUMBER, DATE_B IN TIMESTAMP)
RETURN TIMESTAMP AS
BEGIN
IF SYSDATE - CAST(DATE_B) < 30
RETURN CAST(DATE_A AS DATE) + (((NUM_DAYS/30) + 1)) * 30
ELSE
RETURN CAST(DATE_A AS DATE) + ((((NUM_DAYS/30) + 1)) * 30) + 30
END;
Any help greatly appreciated. Thanks!
Your calculations seem to equate to adding either 30 or 60 days to DATE_A, so I'm not sure why you are dividing and multiplying. I'm also not sure why you're working with dates when the arguments and return value are timestamps; you can stick to timestamps and intervals instead:
CREATE OR REPLACE FUNCTION "DUEDATE" (DATE_A IN TIMESTAMP, NUM_DAYS IN NUMBER, DATE_B IN TIMESTAMP)
RETURN TIMESTAMP AS
BEGIN
IF DATE_B > SYSTIMESTAMP - INTERVAL '30' DAY THEN
RETURN DATE_A + (NUM_DAYS + 30) * INTERVAL '1' DAY;
ELSE
RETURN DATE_A + (NUM_DAYS + 60) * INTERVAL '1' DAY;
END IF;
END;
/
Or
CREATE OR REPLACE FUNCTION "DUEDATE" (DATE_A IN TIMESTAMP, NUM_DAYS IN NUMBER, DATE_B IN TIMESTAMP)
RETURN TIMESTAMP AS
BEGIN
RETURN DATE_A + (NUM_DAYS + CASE WHEN DATE_B > SYSTIMESTAMP - INTERVAL '30' DAY
THEN 30 ELSE 60 END) * INTERVAL '1' DAY;
END;
/
As far as I can see that gets the same results - except it will retain fractional seconds, which are lost when you cast to date.
db<>fiddle demo running both functions for every day this year, and also showing the result of #OldProgrammer's version with and without the CAST(DATE_B AS DATE) - notice (a) that the first two functions retain fractional seconds, and (b) that the third gets different results until you reach 30 days ago (running today, that changes on 2018-06-26 on page 18 of the results).
I would recommend getting a good pl/sql book, or read the oracle documentation. You had a bunch of syntax errors.
CREATE OR REPLACE FUNCTION "DUEDATE" (DATE_A IN TIMESTAMP, NUM_DAYS IN NUMBER, DATE_B IN TIMESTAMP)
RETURN TIMESTAMP AS
BEGIN
IF SYSDATE - DATE_B < 30 THEN
RETURN CAST(DATE_A AS DATE) + (((NUM_DAYS/30) + 1)) * 30;
ELSE
RETURN CAST(DATE_A AS DATE) + ((((NUM_DAYS/30) + 1)) * 30) + 30;
END IF;
END;

create pl sql trigger

I am trying to write a Trigger for before insert to validate a case date.
The case date should be between 5 years before and 7 years after current date.
For example, in 2018 the case date should be from year 2013 to 2025. If the date is out of range the trigger should stop inserting data.
CREATE OR REPLACE TRIGGER ch
BEFORE INSERT
on CASE
FOR EACH ROW
DECLARE
CASN number;
BEGIN
SELECT COUNT(*)
INTO CASN
FROM CASE
WHERE :new.STARTDATE > SYSDATE;
IF (CASN > 0) THEN
RAISE_APPLICATION_ERROR(-20000,'Start DATE CANNOT Be GREATER than today's
date');
END IF;
END;
Here STARTDATE is column of CASE table
This trigger starts when start date is greater than today's date but I need it to run when it's out range as given above.
How do I add an specified interval to the sysdate, so that it could work for the above condition?
The logic you are using in your Trigger is completely wrong. You don't need to get the count from the table using :NEW.STARTDATE. This is something what you are looking for.
CREATE OR replace TRIGGER ch
BEFORE INSERT ON cases
FOR EACH ROW
BEGIN
IF ( :NEW.casedate < SYSDATE - INTERVAL '5' year
OR :NEW.casedate > SYSDATE + INTERVAL '7' year ) THEN
RAISE_APPLICATION_ERROR(-20000,
'CASE DATE should be in range: current date - 5 years and current date + 7 years')
;
END IF;
END;
/
EDIT : I have not added TRUNC on the dates because I'm not sure if you want to consider time component as well while considering date range.If you are ok with just considering days, you may use TRUNC(SYSDATE) in place of just SYSDATE. Modify it accordingly as per your business needs.
Another option is to use CHECK constraint. Although Oracle does not allow you to have
use SYSDATE in a check constraint definition, you may create another column( or reuse existing) that defaults to SYSDATE and apply check constraint on that.
ALTER TABLE CASES ADD ( CURR_DATE DATE DEFAULT SYSDATE );
ALTER TABLE CASES ADD CONSTRAINT
RANGE_CHECK CHECK( casedate > CURR_DATE - INTERVAL '5' YEAR
AND casedate < CURR_DATE + INTERVAL '7' YEAR) ENABLE;
Date arithmetic. Oracle Database enables you to perform arithmetic operations on dates and time stamps in several ways:
Add a numeric value to or subtract it from a date, as in SYSDATE + 7; Oracle Database treats the number as the number of days.
Add one date to or subtract it from another, as in l_hiredate - SYSDATE.
Use a built-in function to “move” a date by a specified number of months or to another date in a week.
Here are some examples of date arithmetic with a date and a number (assume in all cases that the l_date variable has been declared as DATE):
Set a local variable to tomorrow’s date:
l_date := SYSDATE + 1;
Move back one hour:
l_date := SYSDATE - 1/24;
Move ahead 10 seconds:
l_date := SYSDATE + 10 / (60 * 60 * 24);
When you add one date to or subtract it from another, the result is the number of days between the two. As a result, executing this block:
DECLARE
l_date1 DATE := SYSDATE;
l_date2 DATE := SYSDATE + 10;
BEGIN
DBMS_OUTPUT.put_line (
l_date2 - l_date1);
DBMS_OUTPUT.put_line (
l_date1 - l_date2);
END;
returns the following output:
10
-10

Sum of INTERVAL DAY in Oracle [duplicate]

I am trying to sum INTERVAL. E.g.
SELECT SUM(TIMESTAMP1 - TIMESTAMP2) FROM DUAL
Is it possible to write a query that would work both on Oracle and SQL Server? If so, how?
Edit: changed DATE to INTERVAL
I'm afraid you're going to be out of luck with a solution which works in both Oracle and MSSQL. Date arithmetic is something which is very different on the various flavours of DBMS.
Anyway, in Oracle we can use dates in straightforward arithmetic. And we have a function NUMTODSINTERVAL which turns a number into a DAY TO SECOND INTERVAL. So let's put them together.
Simple test data, two rows with pairs of dates rough twelve hours apart:
SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'
2 /
Session altered.
SQL> select * from t42
2 /
D1 D2
-------------------- --------------------
27-jul-2010 12:10:26 27-jul-2010 00:00:00
28-jul-2010 12:10:39 28-jul-2010 00:00:00
SQL>
Simple SQL query to find the sum of elapsed time:
SQL> select numtodsinterval(sum(d1-d2), 'DAY')
2 from t42
3 /
NUMTODSINTERVAL(SUM(D1-D2),'DAY')
-----------------------------------------------------
+000000001 00:21:04.999999999
SQL>
Just over a day, which is what we would expect.
"Edit: changed DATE to INTERVAL"
Working with TIMESTAMP columns is a little more labourious, but we can still work the same trick.
In the following sample. T42T is the same as T42 only the columns have TIMESTAMP rather than DATE for their datatype. The query extracts the various components of the DS INTERVAL and converts them into seconds, which are then summed and converted back into an INTERVAL:
SQL> select numtodsinterval(
2 sum(
3 extract (day from (t1-t2)) * 86400
4 + extract (hour from (t1-t2)) * 3600
5 + extract (minute from (t1-t2)) * 600
6 + extract (second from (t1-t2))
7 ), 'SECOND')
8 from t42t
9 /
NUMTODSINTERVAL(SUM(EXTRACT(DAYFROM(T1-T2))*86400+EXTRACT(HOURFROM(T1-T2))*
---------------------------------------------------------------------------
+000000001 03:21:05.000000000
SQL>
At least this result is in round seconds!
Ok, after a bit of hell, with the help of the stackoverflowers' answers I've found the solution that fits my needs.
SELECT
SUM(CAST((DATE1 + 0) - (DATE2 + 0) AS FLOAT) AS SUM_TURNAROUND
FROM MY_BEAUTIFUL_TABLE
GROUP BY YOUR_CHOSEN_COLUMN
This returns a float (which is totally fine for me) that represents days both on Oracle ant SQL Server.
The reason I added zero to both DATEs is because in my case date columns on Oracle DB are of TIMESTAMP type and on SQL Server are of DATETIME type (which is obviously weird). So adding zero to TIMESTAMP on Oracle works just like casting to date and it does not have any effect on SQL Server DATETIME type.
Thank you guys! You were really helpful.
You can't sum two datetimes. It wouldn't make sense - i.e. what does 15:00:00 plus 23:59:00 equal? Some time the next day? etc
But you can add a time increment by using a function like Dateadd() in SQL Server.
In SQL Server as long as your individual timespans are all less than 24 hours you can do something like
WITH TIMES AS
(
SELECT CAST('01:01:00' AS DATETIME) AS TimeSpan
UNION ALL
SELECT '00:02:00'
UNION ALL
SELECT '23:02:00'
UNION ALL
SELECT '17:02:00'
--UNION ALL SELECT '24:02:00' /*This line would fail!*/
),
SummedTimes As
(
SELECT cast(SUM(CAST(TimeSpan AS FLOAT)) as datetime) AS [Summed] FROM TIMES
)
SELECT
FLOOR(CAST(Summed AS FLOAT)) AS D,
DATEPART(HOUR,[Summed]) AS H,
DATEPART(MINUTE,[Summed]) AS M,
DATEPART(SECOND,[Summed]) AS S
FROM SummedTimes
Gives
D H M S
----------- ----------- ----------- -----------
1 17 7 0
If you wanted to handle timespans greater than 24 hours I think you'd need to look at CLR integration and the TimeSpan structure. Definitely not portable!
Edit: SQL Server 2008 has a DateTimeOffset datatype that might help but that doesn't allow either SUMming or being cast to float
I also do not think this is possible. Go with custom solutions that calculates the date value according to your preferences.
You can also use this:
select
EXTRACT (DAY FROM call_end_Date - call_start_Date)*86400 +
EXTRACT (HOUR FROM call_end_Date - call_start_Date)*3600 +
EXTRACT (MINUTE FROM call_end_Date - call_start_Date)*60 +
extract (second FROM call_end_Date - call_start_Date) as interval
from table;
You Can write you own aggregate function :-). Please read carefully http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dciaggfns.htm
You must create object type and its body by template, and next aggregate function what using this object:
create or replace type Sum_Interval_Obj as object
(
-- Object for creating and support custom aggregate function
duration interval day to second, -- In this property You sum all interval
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number,
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number,
-- Merge parallel summed data
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number,
-- End of query, returning summary result
member function ODCIAggregateTerminate
(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
)
/
create or replace type body Sum_Interval_Obj is
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number
is
begin
actx := Sum_Interval_Obj(numtodsinterval(0,'SECOND'));
return ODCIConst.Success;
end ODCIAggregateInitialize;
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number
is
begin
self.duration := self.duration + ad_interval;
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateIterate;
-- Merge parallel calculated intervals
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number
is
begin
self.duration := self.duration + ctx2.duration; -- Add two intervals
-- return = All Ok!
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateMerge;
-- End of query, returning summary result
member function ODCIAggregateTerminate(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
is
begin
-- return = All Ok, too!
returnValue := self.duration;
return ODCIConst.Success;
end ODCIAggregateTerminate;
end;
/
-- You own new aggregate function:
CREATE OR REPLACE FUNCTION Sum_Interval(
a_Interval interval day to second
) RETURN interval day to second
PARALLEL_ENABLE AGGREGATE USING Sum_Interval_Obj;
/
Last, check your function:
select sum_interval(duration)
from (select numtodsinterval(1,'SECOND') as duration from dual union all
select numtodsinterval(1,'MINUTE') as duration from dual union all
select numtodsinterval(1,'HOUR') as duration from dual union all
select numtodsinterval(1,'DAY') as duration from dual);
Finally You can create SUM function, if you want.

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