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;
Related
PACKAGE specification:
create or replace package p_overload_demo is
function dept_match(v_deptno number) return boolean;
function dept_match(v_dname varchar2) return boolean;
end p_overload_demo;
PACKAGE body:
create or replace package body p_overload_demo is
function dept_match(v_deptno number) return boolean is
cdeptno number;
begin
select count(*) into cdeptno from dept where deptno=v_deptno;
if (cdeptno>0)
then
return true;
DBMS_OUTPUT.put_line('TRUE');
else
return false;
DBMS_OUTPUT.put_line('FALSE');
end if;
end dept_match;
function dept_match(v_dname varchar2) return boolean is
c_dname number;
begin
select count(*) into c_dname from dept where dname=v_dname;
if (c_dname>0)
then
return true;
else
return false;
end if;
end dept_match;
end p_overload_demo;
Test call:
declare
exists boolean;
begin
exists:=p_overload_demo.dept_match(22);
if(exists='True') then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
end;
I get the following error when I test my function:
ORA-06550: line 1, column 56:
PLS-00221: 'DEPT_MATCH' is not a procedure or is undefined
ORA-06550: line 1, column 56:
Can anyone tell me why is this so, and how I can test my overloaded functions?
By this time you would have got your answer but still I would like to post my thoughts.
Overloading should work out of the box if it follows the rules. Besides don't use keywords reserved for oracle as variable name.e.g. exists. Using this gives error sometimes as in this case (not sure if you have noticed or not).
However I have modified the function which reduces few lines of codes and simulated the same with an anonymous block (should work with package also) and it works as expected.
declare
function dept_match(v_deptno number)
return boolean
is
cdeptno number;
begin
select count(*) into cdeptno
from (select 10 deptno from dual)
where deptno=v_deptno;
return (cdeptno>0);
end dept_match;
function dept_match(v_dname varchar2)
return boolean is
c_dname varchar2(100);
begin
select count(*) into c_dname
from (select 'A' dname from dual)
where dname=v_dname;
return (c_dname>0);
end dept_match;
begin
-- with varchar
if dept_match('A') then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
--with number
if dept_match(10) then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
--negative test case
if dept_match('B') then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
end;
/
The PLS-00221 implies you aren't running the anonymous block you showed:
declare
exists boolean;
begin
exists:=p_overload_demo.dept_match(22);
if(exists='True') then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
end;
(which has several issues of its own), but something closer to your execute comment:
begin
p_overload_demo.dept_match(22);
end;
In that form you are calling a procedure called dept_match, and as the error says, there is no procedure with that name. You have two functions with that name, so you can call those, but need to assign the return value to something - as your original anonymous block claims to be doing, so that can't be what you actually ran to get that error.
With the other issues fixed that block:
declare
l_exists boolean;
begin
l_exists:=p_overload_demo.dept_match(22);
if (l_exists) then
dbms_output.put_line('TRUE');
else
dbms_output.put_line('FALSE');
end if;
end;
/
will throw "PLS-00307: too many declarations of 'DEPT_MATCH' match this call" instead, because you've declared both functions with a number argument. You are allowed to do that, you just need to be more explicit when you call them; but as your second function is trying to match a name the declaration is just wrong, and the second one should be decalred with v_dname in varchar2, in both the specification and body. (I see now that you corrected that in an edit to your question, but I'll leave that here anyway.)
With that corrected the block above will work.
db<>fiddle with the function argument data type fixed, and making both calls to both functions; note you don't actually need the l_exists variable so I've removed it from the second one.
I'm probably late to the party but ...There is nothing essentially incorrect with your package. With the exception that the dbms_output statements in the function can never execute as the return statement preceding them exits the function. However, they can be shortened. Since the return type is a boolean, there is no need for the IF statements, just return the comparison (the results of) as it produces the necessary boolean. See demo here. Because db<>fiddle does not support dbms_output (at least I cannot get ti to). I created a log table for messages. (Interesting I looked at #Alex post and dbms_output seems to, wonder what I'm missing?)
I am getting an error "no data found" when I call the following PL/SQL function
FUNCTION get_Deployment_Status(i_deploymentId deployments.pk%type)
RETURN VARCHAR2
IS
o_status varchar2(30);
BEGIN
SELECT distinct Status
into o_status
FROM deployments
WHERE Pk=i_deploymentId;
return o_status;
END;
I pass the deploymentid which is a number and I expect the status to be returned as a string. Is there a better way to do that? why I am getting that error?
Have a look at the following options. Read comments within the code.
Option #1:
FUNCTION get_deployment_status (i_deploymentid deployments.pk%TYPE)
RETURN VARCHAR2
IS
o_status VARCHAR2 (30);
BEGIN
-- This option will return O_STATUS if something is found. If not, NO_DATA_FOUND
-- EXCEPTION section will handle it.
-- You most probably don't need DISTINCT nor TOO_MANY_ROWS handling as it appears that
-- you're dealing with a primary key column; are you? If not, well, you'll have to
-- handle that option as well.
SELECT status
INTO o_status
FROM deployments
WHERE pk = i_deploymentid;
RETURN o_status;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN NULL;
END;
Option #2:
FUNCTION get_deployment_status (i_deploymentid deployments.pk%TYPE)
RETURN VARCHAR2
IS
o_status VARCHAR2 (30);
BEGIN
-- This option will return NULL into O_STATUS if there's nothing found.
-- It won't raise the NO_DATA_FOUND exception.
-- Just like above, if it is a primary key column involved in the WHERE
-- clause, you shouldn't care whether MAX will return correct value or not,
-- because - if it exists - it will be the only value.
SELECT MAX (status)
INTO o_status
FROM deployments
WHERE pk = i_deploymentid;
RETURN o_status;
END;
Is there any way to identify when a pl/sql function is executed in SQL Query and when is executed in a procedure or PL/SQL anonymous block? (I don't want to pass any parameter for manual identification)
The main reason I need that is when a function is executed in a SQL query I wouldn't like to raise an exception in case of failure, I would be satisfied just with a returned value NULL. But the same function when is executed in pl/sql script I want to raise exception.
Thank you in advance.
Why don't you add a parameter to the function to indicate whether or not to throw an exception/return null? When you call the function you can choose the behaviour you need.
create or replace function do_something(p_parameter1 < some_type >
,p_raise_exception varchar2 default 'Y') return < sometype > is
begin
--.. calculating .. .
return result;
exception
when others then
if p_raise_exception is 'Y'
then
raise;
else
return null;
end if;
end;
Alternatively owa_util seems to provide some functionality you can use.
create or replace function do_something(p_parameter1 < some_type >) return < sometype > is
l_owner varchar2(100);
l_name varchar2(100);
l_lineno number;
l_caller_t varchar2(100);
begin
--.. calculating .. .
return result;
exception
when others then
owa_util.who_called_me(l_owner, l_name, l_lineno, l_caller_t)
-- who called me result seems empty when called from sql.
if l_owner is not null
then
raise;
else
return null;
end if;
end;
Of course :
Hiding all errors is bad practise
Well, looking around I found that there is a hack available:
The exception NO_DATA_FOUND isn't propagated when you call PL/SQL in SQL. So you can use this to "return null" instead of get an exception when calling it from SQL:
create or replace function f
return int as
begin
raise no_data_found;
return 1;
end f;
/
select f from dual;
F
null;
declare
v integer;
begin
v := f;
end;
Error report -
ORA-01403: no data found
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.
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.