SQL How to handle an exception in user defined functions? - sql

CREATE OR REPLACE Function fun_Find_Staff_Name( v_staffid IN NUMBER )
RETURN VARCHAR2
IS
staff_name VARCHAR2(60);
CURSOR c_staff IS
SELECT staff_firstName || ' ' || staff_lastName
into staff_name
FROM staff
WHERE staff_id = v_staffid;
BEGIN
OPEN c_staff;
FETCH c_staff INTO staff_name;
CLOSE c_staff;
RETURN staff_name;
END;
This function takes a staff_id and returns the corresponding staff name. I want to handle the exception if the user enters an ID that is invalid and display an error message? Where do I add the exception block?
SET serveroutput ON;
DECLARE
v_staff_ID NUMBER := &StaffID;
v_message VARCHAR2(100);
BEGIN
v_message := fun_Find_Staff_Name(v_staff_ID);
dbms_output.put_line(v_message);
END;
I used this anonyomus block to check if the function works properly.

In my opinion you don't need an explicit cursor, an embedded sql will do the trick:
CREATE OR REPLACE Function fun_Find_Staff_Name
( v_staffid IN NUMBER )
RETURN VARCHAR2
IS
staff_name VARCHAR2(60);
BEGIN
SELECT staff_firstName || ' ' || staff_lastName
INTO staff_name
FROM staff WHERE staff_id = v_staffid;
RETURN staff_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
--in case of an invalid id no data will be found
--you can treat your exception here
--example
DBMS_OUTPUT.PUT_LINE('No data found for this id: ' || v_staffid);
RAISE;
WHEN OTHERS THEN
RAISE;
END fun_Find_Staff_Name ;
And please remember that when you are handling exceptions don't forget to raise it; if not the database will not rollback the transaction in case of an error.
If it yells some errors at the compilation, I am sorry, but I wrote the code without compiling it (in a hurry from my mob).
Cheers and happy coding.

Related

How to find what data caused Oracle function failure?

sorry if this not the right place.
I am doing a SQL SELECT statement, invoking a function. It's a large data dump - about 10,000 records.
I am calling a function to preform some calculations, but its failing.
One ore more of those records has bad data that is causing the function to crash.
Is there any way to see exactly what data caused the crash readily Or should I create some code to run the function by hand for each of 10,000 records? I could create code that generates the input data fairly straightforwardly, then run the function like this SELECT MY_FUNCT(1,1,1) FROM DUAL; but I am wondering if there is a better way.
For reference I am running the SQL query like this.
SELECT
MY_FUNCT(A.FOO, A.BAR)
FROM TABLE A
WHERE ....;
As others have said, you just need to handle the error and not raise it all the way. A neat way of doing this would be to create a wrapper function for your function that sometimes fails, you can declare this function within your select query using a with pl/sql clause:
Let's say this is your function that sometimes fails
create or replace function my_funct (inputnumber number)
return varchar2
is
sString varchar2(200);
begin
if inputnumber = 42 then
raise_application_error(-20001,'UH OH!');
end if;
sString := 'Input: '||inputnumber;
return sString;
end my_funct;
/
We can define a function that takes the same inputs, and just calls this function, then we just need to add some error handling (obviously never just rely on dbms_output to capture errors, this is just to make it obvious):
function my_funct_handle (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
And we can then just stick that in our query using with function
with
function my_funct_handler (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
select my_funct_handler (id), string_col
from as_table;
/
I get both the dbms_output text to describe the error and the ID but I could also filter on the results of that function to only show me the erroring rows:
with
function my_funct_handle (inputnumber number)
return varchar2
is
begin
return my_funct (inputnumber => inputnumber);
exception when others then
dbms_output.put_line(sqlerrm||' at '||inputnumber);
return 'ERROR';
end;
select my_funct_handle (id), string_col
from as_table
where my_funct_handle (id) = 'ERROR';
/
MY_FUNCT_HANDLE(ID)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
STRI
----
ERROR
blah
ORA-20001: UH OH! at 42
ORA-20001: UH OH! at 42
(I get two errors shown in the dbms_output output as the function can be called multiple times - once as part of the select list and once as part of the where clause.)
One option is to handle the exception properly, e.g.
create or replace function my_funct(par_foo in number, par_bar in number)
return number
is
retval number;
begin
select sal
into retval
from emp
where ename = par_foo
and deptno = par_bar;
return par_foo/par_bar;
exception --> this
when no_data_found then
return null;
end;
If you want, you can even log those errors. How? Make the function autonomous transaction (so that it could write into the log table; you'll have to commit that insert). Store all relevant information (including SQLERRM). Once your code finishes, check what's written in the log file and then decide what to do.
Or, you could even continue current task by enclosing that select into its own begin-exception-end block within a loop, e.g.
begin
for cur_r in (select ... from ...) loop
begin
-- your current SELECT
SELECT
MY_FUNCT(A.FOO, A.BAR)
FROM TABLE A
WHERE ....;
exception
when others then
dbms_output.put_line(cur_r.some_value ||': '|| sqlerrm);
end;
end loop;
end;
one better approach is to create a error handler Package/Procedure which will write it into a table and call it from the function, this way all the errors will be captured in a Oracle table.
--- untested -- You can create other columns to capture the function name, date, and other details in the error table.
PROCEDURE SP_ERROR_INS_ERR_COMMON (n_ERR_CODE NUMBER, c_ERR_SOURCE VARCHAR2, n_ERR_LINE NUMBER, c_ERR_DESC VARCHAR2, C_USER_COMMENT VARCHAR2 ) IS
n_Log_Id NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
n_Log_Id := 0;
INSERT INTO ERROR_LOG_COMMON
(ERROR_CODE, ERROR_SOURCE, ERROR_LINE, ERROR_DESCRIPTION, USER_COMMENT)
VALUES
(n_ERR_CODE, c_ERR_SOURCE, n_ERR_LINE, c_ERR_DESC, C_USER_COMMENT
);
COMMIT;
raise_application_error( -20001,SUBSTR(SQLERRM, 1, 200));
END SP_ERROR_INS_ERR_COMMON;
In your function you can call the error
EXCEPTION
WHEN OTHERS
THEN
vn_errcode := SQLCODE;
vc_errmsg := SUBSTR (SQLERRM, 1, 4000);
sp_error_ins_err_common(vn_ErrCode,
'SP_RP_DIM_COMPARE', NULL, vc_ErrMsg, substr('batch_id ' || g_BATCH_ID ||
l_str_err_msg,1,4000) );
RAISE;

How do you add cursor into a procedure in PL/SQL block?

I need to create a procedure that accepts first and last letter of a person's name and returns the totalcost and total items bought by him. I have no idea how to use the cursor with it also i'm thinking i have to use a for loop and exception handling since there can be many persons with the same starting and ending letter's.
So far I have come up with this:
create or replace procedure total_spent(v_fname IN
saleinv.cname%TYPE,v_lname IN saleinv.cname%TYPE.v_netspend OUT
saleinv.net%TYPE,v_totalpurch OUT NUMBER) AS
Begin
select sum(net+tax),count(net) into v_netspend,v_totalpurch from saleinv
where cname LIKE '&v_fname%&v_lname';
END;
/
ACCEPT p_fname PROMPT 'Enter The first letter of customer's name'
ACCEPT p_lname PROMPT 'Enter the last letter of customer's name'
BEGIN
totalspent('&p_fname',&'p_lname');
Came up with one solution, with small modifications (by the way, be more specific with parameters names, that there would be no interpretations):
procedure total_spent(
p_fname_first_letter in varchar2,
p_lname_last_letter in varchar2,
p_netspend out number,
p_totalpurch out number) as
cursor c_net(
p_fname_first_letter varchar2,
p_lname_last_letter varchar2) is
select
sai.cname,
sum(sai.net + sai.tax) net_spend,
count(sai.net) total_purch
from
saleinv sai
where
upper(sai.cname) like upper(p_fname_first_letter) || '%' || upper(p_lname_last_letter)
group by
sai.cname;
l_found_cust boolean := false;
l_show_err boolean := false;
l_cust_list varchar2(100);
Begin
p_netspend := 0;
p_totalpurch := 0;
for l_sai_rec in c_net(
p_fname_first_letter,
p_lname_last_letter)
loop
if not l_found_cust then
p_netspend := l_sai_rec.net_spend;
p_totalpurch := l_sai_rec.total_purch;
l_cust_list := l_sai_rec.cname;
l_found_cust := true;
else
l_cust_list := l_cust_list || '; ' || l_sai_rec.cname;
l_show_err := true;
end if;
end loop;
if l_show_err then
raise_application_error(-20101, 'Found more customers with provided parameters. Customers: '||l_cust_list);
end if;
end;
It uses cursor, as you mentioned. Of course, there is a better way to do this, without cursor, that also fulfills your logic requirements (less code). If you are willing to know, feel free to ask.

Error when calling PL/SQL function

I have created a PL/SQL function that is supposed to be adding a given number of records to a table with the help of new_dept_new sequence that I've created earlier. The function compiled successfully. But I'm getting an error when calling the function:
BEGIN
dbms_output.put_line(myfunction3(3, 'OPERATIONS', 'NEW YORK'));
END;
Error report -
ORA-06503: PL/SQL: Function returned without value
The function:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
COMMIT;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
END;
Why is the function returned without value?
EDIT:
After receiving feedback from the comments I've edited the function and now the values are inserted into the table and the timestamp value is returned. I also want an application error to be raised if 0 rows were inserted.
So I did the following:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept (deptno, dname, loc)
VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
IF SQL%ROWCOUNT = 0 THEN
raise_application_error (-20001, 'ERROR Processing Request for Department: ' || dept_name_in);
END IF;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
END;
However if I'm calling the function like this and zero records are inserted, I'm not getting my custom error message.
BEGIN dbms_output.put_line(myfunction3(0, 'DEV_OPS', 'NEW YORK')); END;
How can I fix this?
Nothing seems to be wrong with your functionexcept for the exception block.I was able to simulate the scenario with the table and column names you used and it worked perfectly fine. If there are other issues like missing table,sequence it would not have compiled for you. So, the only reason for it to fail is your insert statement. It could be either constraint violation, length of value exceeds the column length limit etc.
You can make a slight change in your exception block by adding a raise statement.This will help you to identify the actual error in your calling program.
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
RAISE;
Additionally, Never run insert statements like
INSERT INTO dept VALUES (...)
It is not a good coding practice and difficult to know which order it is going to insert especially when there are many columns.
Always mention the columns explicitly
INSERT INTO dept ( dept_id,dname,loc) VALUES (..,..,..);
There is some exception in your function that causes your function to hit the exception block. There is no return statement in your exception block and hence the error. Add a RAISE statement to see what's causing the exception.

wrong number or types of arguments in call (while calling a procedure/function)

If I want to catch this error using exception handling, what are the things that I need to take care of?
wrong number or types of arguments in call (while calling a
procedure/function)
I trying was in different way. Could you please explain. I have a function:
create or replace function test5(v varchar2) return varchar as
begin
execute immediate 'begin sweet.g:=:v;end;'
using in v;
return sweet.g;
exception
when others then
return sqlcode||' '||sqlerrm;
end test5;
And a package spec and body:
create or replace package SWEET as
function c ( v varchar2,V2 VARCHAR2) return varchar2;
g varchar(100);
end;
/
create or replace package body SWEET as
function c(v varchar2, V2 varchar2) return varchar2 as
begin
return v||'hi'|| V2;
end c;
end;
/
when I execute the statement below, I was not able to catch 'wrong number or type of arguments'
select test5(sweet.c(,'hello')) from dual;
You should be able to get most of the answers in the PL/SQL manual, but when you are trying to trap an error that isn't one of the predefined ones you have to do something like:
DECLARE
deadlock_detected EXCEPTION;
PRAGMA EXCEPTION_INIT(deadlock_detected, -60);
BEGIN
... -- Some operation that causes an ORA-00060 error
EXCEPTION
WHEN deadlock_detected THEN
-- handle the error
END;
replacing -60 with your actual error, and deadlock_detected with whatever you wish to call it.
Let's say you have a procedure that takes in two numbers as arguments and outputs them:
create procedure testProc (p_param1 in number, p_param2 in number) is
begin
dbms_output.put_line('params: ' || p_param1 || ' ' || p_param2);
end;
If you execute this:
begin
testProc(13,188);
end;
You get output of: params: 13 188
If you do this:
begin
testProc(13);
exception when others then
dbms_output.put_line('SQLERRM: ' || SQLERRM);
end;
You get an error: PLS-00306: wrong number or types of arguments in call to 'TESTPROC'
To prevent this and catch the error, you can use dynamic SQL:
declare
v_sql varchar2(50);
v_result number;
begin
v_sql := 'begin testProc(13); end;';
execute immediate v_sql into v_result;
exception
when others then
dbms_output.put_line('SQLERRM: ' || SQLERRM);
end;
That will execute, and the error message will be displayed to dbms_output. In the when others then block you can write any logic you want for what should happen at that point.

how handle table or view does not exist exception?

I have a set of table names, let say 150. Each table have mail_id column, now I want to search one mail_id in all of the table. For that I wrote one Plsql block. When I loop through the set of table some tables do not exists so it raises an exception. I have exception handling block to handle that exception. Now I want to loop entire table even though it raise an exception? Any idea? Actually my block didn't handle that particular exception!
declare
my_mail_id varchar2(50):='xyaksj#jsm.com';
tmp_table varchar2(125);
type varchar_collector is table of varchar2(255);
var varchar_collector;
table_does_not_exist exception;
PRAGMA EXCEPTION_INIT(table_does_not_exist, -00942);
begin
for cntr in (select table_name from user_tables)
loop
tmp_table:=cntr.table_name;
dbms_output.put_line(tmp_table);
for mail in (select email_address from tmp_table where lower(email_address) like '%my_mail_id%' )
loop
dbms_output.put_line(tmp_table);
end loop;
end loop;
exception
when no_data_found then
dbms_output.put_line('email address not found');
WHEN table_does_not_exist then
dbms_output.put_line('table dose not exists');
WHEN OTHERS THEN
--raise_application_error(-20101, 'Expecting at least 1000 tables');
IF (SQLCODE = -942) THEN
--DBMS_Output.Put_Line (SQLERRM);
DBMS_Output.Put_Line ('in exception');--this exception not handled
ELSE
RAISE;
END IF;
end;
Just handle your exceptions in anonymous block inside the loop.
DECLARE
my_mail_id VARCHAR2(50) := 'xyaksj#jsm.com';
tmp_table VARCHAR2(125);
TYPE varchar_collector IS TABLE OF VARCHAR2(255);
var varchar_collector;
table_does_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT(table_does_not_exist, -00942);
BEGIN
FOR cntr IN (SELECT table_name FROM user_tables)
LOOP
BEGIN
tmp_table := cntr.table_name;
dbms_output.put_line(tmp_table);
FOR mail IN (SELECT email_address
FROM tmp_table
WHERE lower(email_address) LIKE '%my_mail_id%')
LOOP
dbms_output.put_line(tmp_table);
END LOOP;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('email address not found');
WHEN table_does_not_exist THEN
dbms_output.put_line('table dose not exists');
WHEN OTHERS THEN
--raise_application_error(-20101, 'Expecting at least 1000 tables');
IF (SQLCODE = -942)
THEN
--DBMS_Output.Put_Line (SQLERRM);
DBMS_Output.Put_Line('in exception'); --this exception not handled
ELSE
RAISE;
END IF;
END;
END LOOP;
END;
If you're selecting from user_tables and finding that some of them do not exist then you're probably trying to query tables that are in the recycle bin (their names begin BIN$).
If so, change your query to:
select table_name
from user_tables
where dropped = 'NO';
You should replace your second cursor with a call to execute immediate also, constructing the query by concatenating in the table_name not just using a variable as the table name, and you might as well construct the query as:
select count(*)
from table_name
where lower(email_address) like '%my_mail_id%'
and rownum = 1;
That way you'll retrieve a single record that is either 0 or 1 to indicate whether the email address was found, and no need for error handling.
try below code...
DECLARE
foo BOOLEAN;
BEGIN
FOR i IN 1..10 LOOP
IF foo THEN
GOTO end_loop;
END IF;
<<end_loop>> -- not allowed unless an executable statement follows
NULL; -- add NULL statement to avoid error
END LOOP; -- raises an error without the previous NULL
END;