How to validate YYYYMMDD date given as parameter PL/SQL - sql

I have a SP called ADD_COMPLEX_SALE. It gets 4 parameters : custid, prod id, qty and date(varchar2).
I need to validate the date and check that it is in the right format. It is being sent to the procedure as YYYYMMDD.
This is what I have so far:
IF (LENGTH(pdate) != 8) THEN
raise_application_error (-20093,' Date not valid');
ELSIF (LENGTH(pdate) = 8)THEN
vDATE := CONVERT(VARCHAR(10), pdate(), 111) AS [YYYY/MM/DD];
END IF;
IF (pdate != 'YYYY/MM/DD') THEN
raise_application_error (-20093,' Date not valid');
END IF;
Basically the idea is too convert it to a 'real' date format so that I can know for sure it is in the proper format. Except I get this error.
Still very new pl/sql so any help will be appreciated.
Error(42,49): PLS-00103: Encountered the symbol "AS" when expecting one of the following: . ( * % & = - + ; < / > at in is mod remainder not rem <> or != or ~= >= <= <> and or like like2 like4 likec between || multiset member submultiset
UPDATE:
Changed code to the following :
vDATE := TO_DATE(pdate, 'yyyymmdd');
IF (pdate != vDATE) THEN
raise_application_error (-20093,' Date not valid');
END IF;
IF (LENGTH(vDATE) != 8) THEN
raise_application_error (-20093,' Date not valid');
END IF;
Now getting this error:
ORA-20000: Another error occured ORA-01861: literal does not match format string
EDIT: Last update error was due to Parameter being varchar2 and me forgetting to INSERT vDATE instead of pdate after the conversion was done.

convert(varchar(10), pdate(), 111) appears to be an attempt to use the SQL Server convert function. That's not going to work in Oracle.
I'd just do something like
DECLARE
l_dt date;
BEGIN
l_dt := to_date( pdate, 'yyyymmdd' );
EXCEPTION
WHEN others
THEN
raise_application_error( -20001, pdate || ' is not a date in the format YYYYMMDD' );
END;
Of course, if you want to do multiple checks so that you can throw a different exception if the length is incorrect or add some checks to ensure that the date is reasonable (i.e. must be within the last 100 years or no more than 100 years in the future, etc.) you could do that after the to_date conversion.

CREATE PROCEDURE DATECHECK(pdate VARCHAR2) AS
err_date EXCEPTION;
vdate DATE;
BEGIN
BEGIN
vdate := to_date(pdate,'yyyymmdd');
EXCEPTION
WHEN OTHERS THEN
RAISE err_date;
END;
EXCEPTION
WHEN err_date THEN
raise_application_error(-20090, 'DATE ERROR');
END;

Related

PL/SQL Invalid object identifier in creating procedure with date field parameter

Im fairly new pl/sql and im trying to create procedure that can take a date parameter from input, find and update the matching join date row(s) from student table I am working on but i cant seem to get a valid date and i'm not sure i properly configured my procedure from input your help would be appreciated, this is my code currently.
CREATE OR REPLACE PROCEDURE dateChanger (p_join_date IN DATE)
IS p_date DATE;
p_month VARCHAR2(3);
p_year VARCHAR2(4);
p_change VARCHAR2(11);
v_month VARCHAR2(3);
BEGIN
p_date := to_date(p_join_date, 'dd-mon-yyyy');
p_month := Extract(MONTH FROM p_date);
p_year := Extract(YEAR FROM p_date);
v_month := to_char(Extract(MONTH FROM student.join_date%TYPE), 'MON');
--expected result 01-(Month from p_date)-(Year from p_date) eg. 01-JUL-2021
p_change := '01-'+to_char(p_month, 'MON')+'-'+to_char(p_year, 'YYYY');-- date put back together
UPDATE Week03.t_student SET join_date = to_date(p_change, 'dd-mon-yyyy')
WHERE v_month = p_month; -- table updated where months match
RETURN(p_date || '-----' || p_change);
END;
/
ACCEPT jDate Date PROMPT 'Please Enter a valid date as dd-MON-YYYY eg. 13-JUL-2021';
DECLARE
dc VARCHAR2(11);
l_date student.join_date%type := &jDate; -- input from user saved to date type variable
BEGIN
dc := DATECHANGER(l_date);
SELECT DATECHANGER(l_date) INTO dc from dual;
END;
/
When i enter the date as 'dd-mon-yyy' or 'yyyy-mon-dd' and many other variations i get a pls 00905 & 00201 error any help would be appreciated. it seems the date input refuses all the variations i've tried.
From documentation:
DATE
Makes reply a valid DATE format. If the reply is not a valid DATE
format, ACCEPT gives an error message and prompts again. The datatype
is CHAR.
That means in this case result of accept of data is char format.
You should change you code a bit.
in announmous block
l_date student.join_date%type := &jDate; => l_date varchar2(100) := '&jDate'
and in procedure
(p_join_date IN DATE) => (p_join_date in varchar2)
Yea so for future references to anyone who may need to complete a similar task to mine here is how i solved my problem thanks a bit to #Arkadiusz Ɓukasiewicz
CREATE OR REPLACE PROCEDURE dateChanger (p_join_date IN DATE)
BEGIN
UPDATE Week03.t_student SET join_date = trunc(join_date, 'MM');
END;
/
SELECT * FROM student;
BEGIN
dateChanger(to_date('2018-09-01', 'yyyy-mm-dd'));
END;
/

How to replace Invalid Date Part (Day / Month) to default 01 if Year is valid

Need Oracle SQL or stored procedure to convert invalid day or month in the date field to 01 if Year is a valid year. The input is in varchar format.
For example, if input value is 20132016 (MMDDYYYY), the Year is a valid value 2016, now I have to check Day and Month.
In the example above, Day is also valid as it's 13, but the month is invalid with a value of 20. So I have to convert month to 01 (default). So the output expected is 01132016.
I need this in my project to to predict someone age, So instead of inserting Null for invalid Date of Birth, I want to retain part of the date value importantly the Year.
I have a function to return valid date but this function does not return the expected result I am looking for.
CREATE OR REPLACE FUNCTION FUNC_CHK_DATE (P_INPUT IN VARCHAR2)
Return Date
Is
v_date DATE := NULL;
BEGIN
V_DATE := TO_DATE( P_INPUT, 'YYYY/MM/DD');
RETURN V_DATE;
EXCEPTION
WHEN OTHERS THEN
RETURN null;
END FUNC_CHK_DATE;
Your first mistake is here:
V_DATE := TO_DATE( P_INPUT, 'YYYY/MM/DD');
You say the expected format is 'MMDDYYYY' (e.g. '20132016'). So why are you trying to convert a string formatted 'YYYY/MM/DD'?
Then, when conversion fails, you are not even trying to detect the erroneous part and repair it, but simply return null. So how can this possibly return the desired date?
Let's start with an agorithm:
Check whether the input string is 8 digits exactly. If not, return null.
Convert to date. If that works, return the date.
Otherwise we'll check the year. Is it zero (which is not allowed in Oracle), we make it 0001.
Convert to date again. If that works, return the date.
Otherwise we'll check the month. Is it zero or greater than 12, we make it 01.
Convert to date again. If that works, return the date.
Otherwise the day is incorrect for the month and year, so we make it 01.
Convert to date again and return the date.
I'm using a loop for convenience here:
CREATE OR REPLACE FUNCTION func_chk_date (p_input IN VARCHAR2)
RETURN DATE
IS
v_date DATE;
v_datestring CHAR(8);
BEGIN
IF NOT REGEXP_LIKE(p_input, '^[[:digit:]]{8}$') THEN
RETURN NULL;
END IF;
v_datestring := p_input;
LOOP -- until conversion okay
BEGIN
v_date := TO_DATE(v_datestring, 'MMDDYYYY');
EXIT; -- conversion okay
EXCEPTION WHEN OTHERS THEN
IF SUBSTR(v_datestring, 5, 4) = '0000' THEN
v_datestring := SUBSTR(v_datestring, 1, 4) || '0001';
ELSIF TO_NUMBER(SUBSTR(v_datestring, 3, 2)) NOT BETWEEN 1 AND 12 THEN
v_datestring := SUBSTR(v_datestring, 1, 2) || '01' || SUBSTR(v_datestring, 5, 4);
ELSE
v_datestring := '01' || SUBSTR(v_datestring, 3, 6);
END IF;
END;
END LOOP;
RETURN V_DATE;
END FUNC_CHK_DATE;
Nesting your tests is one way:
CREATE OR REPLACE FUNCTION func_chk_date (p_input IN VARCHAR2)
RETURN DATE
IS
v_date DATE := NULL;
BEGIN
v_date := TO_DATE (p_input, 'YYYY/MM/DD');
RETURN v_date;
EXCEPTION
WHEN OTHERS
THEN
BEGIN
v_date := TO_DATE (SUBSTR (p_input, 1, 4), 'YYYY');
RETURN v_date;
EXCEPTION
WHEN OTHERS
THEN
RETURN NULL;
END;
END func_chk_date;
You create a function that traps the date errors that you've want to attempt correcting. The following is just one way.
create or replace function func_chk_date (date_string_in varchar2
,date_format_in varchar2 default 'mmddyyyy'
)
return date
is
bad_mon_e exception;
PRAGMA EXCEPTION_INIT(bad_mon_e, -01843);
bad_day_e exception;
PRAGMA EXCEPTION_INIT(bad_day_e, -01839);
bad_mon_day_e exception;
PRAGMA EXCEPTION_INIT(bad_mon_day_e, -01847);
date_value_l date;
function fix_dd
return varchar2
is
begin
return substr(date_string_in,1,2) || '01' || substr(date_string_in,5);
end fix_dd;
function fix_mm
return varchar2
is
begin
return '01' || substr(date_string_in,3);
end fix_mm;
begin
begin
date_value_l:= to_date( date_string_in, date_format_in);
exception
when bad_mon_e then
date_value_l:= func_chk_date (date_string_in => fix_mm
,date_format_in => date_format_in
);
when bad_day_e
or bad_mon_day_e then
date_value_l:=func_chk_date (date_string_in => fix_dd
,date_format_in => date_format_in
);
when others then
null; -- << Actually a very bad idea at least log the error >>
end ;
return date_value_l;
exception
when others then
dbms_output.put_line('Other Error:'|| sqlerrm);
end func_chk_date;
/
Some test data:
begin
dbms_output.put_line ('05052017 returned ' || func_chk_date('05052017'));
dbms_output.put_line ('02292017 returned ' || func_chk_date('02292017'));
dbms_output.put_line ('02292016 returned ' || func_chk_date('02292016'));
dbms_output.put_line ('13292016 returned ' || func_chk_date('13292016'));
dbms_output.put_line ('20402040 returned ' || func_chk_date('20402040'));
dbms_output.put_line ('0505yyyy returned ' || func_chk_date('0505yyyy'));
end ;
The function works as is but is limited to the date format "mmddyyyy". If you want a different format you'll need to modify the fix-mm and/or the fix_dd functions. Also this is a recursive routine so you'll need to guarantee there is a exit path.

Sysdate as char instead of date

I have a query.
i have a config table AB where a row is marked as "sysdate-360"
col1 ||col2
AB || sysdate-360
BC || sysdate -2
When i write a procedure to get date value from the config table AB i used.
v_date varchar(20);
cursor c1 as
select col2 from AB;
then
for ab_date in c1
loop
select ab_date.col2 into v_date from dual;
v_sql := 'delete from any_table where load_date <='||v_date;
execute immediate v_sql ;
commit;
end loop;
The procedure is compiled but when i execute I'm getting below error
ORA-01722: invalid number
ORA-06512: at "procedure", line 46
ORA-06512: at line 1
01722. 00000 - "invalid number"
*Cause:
*Action:
The sysdate -360 is considered as char but not as date since SYSDATE is itself a date right?
Please help.
If you handle only expressions SYSDATE +/- N I'd suggest to modify the config table as follows
ID SYSDATE_OFFSET
-- --------------
AB -360
BC -2
So you have a numeric offset to sysdate which can be queried this way:
select sysdate + (select SYSDATE_OFFSET from config where id = 'AB') s_360
from dual;
S_360
-----------------
25.02.16 21:46:50
So you may open a cursor with the query above.
If you can't change the table - define a view that removes the string sysdate and converts to number!
This looks quite fine to me. But why do you select into v_date? You get a string from the cursor which you can concatenate directly:
for ab_date in c1 loop
v_sql := 'delete from any_table where load_date <=' || ab_date.col2;
execute immediate v_sql ;
commit;
end loop;
At last this is simply the concatanation of two strings 'delete from any_table where load_date <=' and 'sysdate-360' which makes 'delete from any_table where load_date <= sysdate-360' - exactly the SQL string you want.
(That would look even better with a proper column name such as date_expression instead of col2.)
More elaborate explanation: The cursor gets you the string 'sysdate-360'. The query select ab_date.col2 into v_date from dual; is simply v_date := ab_date.col2;. But is v_date a string? If not, you get a conversion error, because 'sysdate-360' is just a string, nothing more. If you expect the DBMS to see that the string contains 'sysdate' which also happens to be the name of a system variable for the current time and then convert this magically, you expect to much.
Try the to_date function
TO_DATE(v_date)

Oracle Date Format with input Parametres - PLSQL

I'm getting an error as follows:
ORA-01858 - a non-numeric character was found where a numeric character was expected.
Although it isn't really accurate, I believe it's related to the date formatting in the following lines.
ELSIF par_report_eff_date_start IS NOT NULL AND par_report_eff_date_start = to_date(par_report_eff_date_start, 'fxdd-mm-yyyy')
and
ELSIF par_report_eff_date_end IS NOT NULL AND par_report_eff_date_end = to_date( par_report_eff_date_end ,'fxDD-MM-YYYY')
I'm trying to get the parameters to render date in the format of 'dd/mm/yyyy' as it is passed in, but i'm not sure how to get around this.
I have looked, but have limited web access at work, so I can't use the regular sites.
procedure collect_mon_comm_bal_data_part (
par_report_eff_date_start DATE DEFAULT NULL, --dd/mm/yyyy;
par_report_eff_date_end DATE DEFAULT NULL) --dd/mm/yyyy;
is
v_report_eff_date_start DATE;
v_report_eff_date_end DATE;
BEGIN
IF par_report_eff_date_start IS NULL
THEN
-- Oracle job runs at the beginning of each month
select trunc(trunc(sysdate,'Mon')-1,'Mon')
into v_report_eff_date_start
from dual; -- Start of month Var
ELSIF par_report_eff_date_start IS NOT NULL AND par_report_eff_date_start = to_char(par_report_eff_date_start, 'fxdd/mm/yyyy')
THEN
v_report_eff_date_start := par_report_eff_date_start;
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_start || 'Is The Start Date');
ELSE
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_start || 'Is is the wrong format, needs tp be in dd/mm/yyyy');
GOTO the_end;
END IF;
IF par_report_eff_date_end IS NULL
THEN
-- Oracle job runs at the beginning of each month
select trunc(sysdate,'MM')-1
into v_report_eff_date_end
from dual; -- Start of month Var
ELSIF par_report_eff_date_end IS NOT NULL AND par_report_eff_date_end = to_char(par_report_eff_date_end, 'fxdd/mm/yyyy')
THEN
v_report_eff_date_end := par_report_eff_date_end;
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_end || 'Is The Start Date');
ELSE
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_end || 'Is is the wrong format, needs tp be in dd/mm/yyyy');
GOTO the_end;
END IF;
END;
You don't want to convert a date to a date. You want to convert it to a character string. So try:
to_char(par_report_eff_date_start, 'fxdd-mm-yyyy')
I'm not sure what the rest of the logic is supposed to be doing. But, you use to_date() to convert a value to a date and to_char() to convert a value to a string.
I notice that you have parameter par_report_eff_date_start, and a variable v_report_eff_date_start. You probably want to compare those in the following line:
ELSIF par_report_eff_date_start IS NOT NULL AND par_report_eff_date_start = to_char(par_report_eff_date_start, 'fxdd/mm/yyyy')
But now you are comparing the date parameter with a text value derived from the parameter value. I don't think that's what you want.
A date is just a date, and you only have to think about format masks when converting it to another data type (like VARCHAR2).
So why not use:
ELSIF par_report_eff_date_start IS NOT NULL AND trunc(par_report_eff_date_start) = trunc(sysdate, 'Mon')
I've added trunc() to the parameter value, and because you want the first day of the month (I guess!!), trunc(sysdate, 'Mon') will do just fine.
Thank You Gordon, I think I've found a way around it.
procedure collect_mon_comm_bal_data_part (
par_report_eff_date_start VARCHAR2 DEFAULT NULL, --dd/mm/yyyy;
par_report_eff_date_end VARCHAR2 DEFAULT NULL) --dd/mm/yyyy; is
v_report_eff_date_start DATE; v_report_eff_date_end DATE;
BEGIN
IF par_report_eff_date_start IS NULL
THEN
-- Oracle job runs at the beginning of each month
select trunc(trunc(sysdate,'Mon')-1,'Mon')
into v_report_eff_date_start
from dual; -- Start of month Var
ELSIF par_report_eff_date_start IS NOT NULL AND to_date(par_report_eff_date_start, 'dd/mm/yyyy') = to_date(par_report_eff_date_start,'fxdd/mm/yyyy')
THEN
v_report_eff_date_start := to_date(par_report_eff_date_start, 'dd/mm/yyyy');
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_start || 'Is The Start Date');
ELSE
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_start || 'Is is the wrong format, needs tp be in dd/mm/yyyy');
GOTO the_end; END IF;
IF par_report_eff_date_end IS NULL
THEN
-- Oracle job runs at the beginning of each month
select trunc(sysdate,'MM')-1
into v_report_eff_date_end
from dual; -- Start of month Var
ELSIF par_report_eff_date_end IS NOT NULL AND to_date(par_report_eff_date_end, 'dd/mm/yyyy') = to_date(par_report_eff_date_end,'fxdd/mm/yyyy')
THEN
v_report_eff_date_end := to_date(par_report_eff_date_end, 'dd/mm/yyyy');
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_end || 'Is The Start Date');
ELSE
DBMS_OUTPUT.PUT_LINE(par_report_eff_date_end || 'Is is the wrong format, needs tp be in dd/mm/yyyy');
GOTO the_end; END IF;
So I want it to default to a monthly run, for the previous month if the parameters are null. And I want it to run for selected dates, should they be put in manually at execution, this way I can run it for a year period to build up a proper sample of data, that will be updated as it changes going forward.
I have tried to pass in bogus dates, and null dates and they hit the exception or run for a month of data as expected, so this works for me.
Thanks again guys, I really the help!

Converting a String to Date and raising an exception when given String is invalid

Here is my code given below. I want if the date given is incorrect it should display a message that is "The format provided is incorrect".
But I cant get it to raise the exception.
Is the exception used incorrect which other exception provided by oracle can I use for this case?
Create or Replace Procedure A1SF_TESTDATE
(
pDateStr Varchar2
)As
tDate Date;
Begin
tdate := TO_DATE(pDateStr, 'yyyymmdd');
dbms_output.put_line(tdate);
Exception
When INVALID_NUMBER Then
dbms_output.put_line('The format provided is incorrect');
End;
There are many exception which can be thrown by the TO_DATE function. Examples:
ORA-01843 - Invalid month value
ORA-01847 - Invalid day value
ORA-01830 - date format picture ends before converting entire input string
...
You can catch them as in the following example (with only one exception):
Create or Replace Procedure A1SF_TESTDATE
(
pDateStr Varchar2
-- you must do this for every oracle exception number which will you catch
bad_month EXCEPTION;
PRAGMA EXCEPTION_INIT (bad_month, -01843);
)As
tDate Date;
Begin
tdate := TO_DATE(pDateStr, 'yyyymmdd');
dbms_output.put_line(tdate);
Exception
When bad_month Then
dbms_output.put_line('The format provided is incorrect');
End;
But for that you must define n pragmas!
The easier solution, which I prefer, is:
Create or Replace Procedure A1SF_TESTDATE
(
pDateStr Varchar2
)As
tDate Date;
Begin
tdate := TO_DATE(pDateStr, 'yyyymmdd');
dbms_output.put_line(tdate);
Exception
-- every exception will be catched
When others Then
dbms_output.put_line('The format provided is incorrect! Because: ' || SQLERRM);
End;
A possible message for SQLERRM is ORA-01847: day of month must be between 1 and last day of month.