I'm currently working on logging errors in a procedure. The goal of this procedure is to be called upon in exception handlers in the other packages in the DB and log the errors that each program encounters. below is my code.
CREATE OR REPLACE PROCEDURE APMS.test_procedure AS
procedure write_error_log (errcode number, errstr varchar2) is
pragma autonomous_transaction;
-- this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_line_no)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
'line number');
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
exception when others then
write_error_log(sqlcode,sqlerrm);
raise;
END test_procedure;
/
I'm currently just inducing an error in my mock_data table to log the error in the error_log table and see if its functional I just cant figure out how to log the line number column. I'm a complete beginner so any help would appreciated. Addiotionally, If anybody knows how I would be able to use this procedure in other packages/procedures to log the errors in other packages that would be awesome as well. I'm here to learn so any feedback is appreciated, I can further expand on this post if i'm not being clear.
Try using DBMS_UTILITY.FORMAT_ERROR_BACKTRACE. You can look here for more information.
Something like this should make works your code:
CREATE OR REPLACE PROCEDURE APMS.test_procedure AS
procedure write_error_log (errcode number, errstr varchar2,errline varchar2) is
pragma autonomous_transaction;
-- this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_line_no)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
errline);
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
exception when others then
write_error_log(sqlcode,sqlerrm,DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
raise;
END test_procedure;
Use the following to get the call stack:
dbms_utility.format_error_stack
dbms_utility.format_error_backtrace
For example,
SQL> declare
2 v1 integer := 1;
3 v2 integer := 0;
4 v3 integer;
5 procedure p1 (v1 in integer, v2 in integer, v3 out integer) is
6 begin
7 v3 := v1 / v2;
8 end;
9 procedure p2 (v1 in integer, v2 in integer, v3 out integer) is
10 begin
11 p1 (v1, v2, v3);
12 end;
13 begin
14 p2 (v1, v2, v3);
15 exception
16 when others then
17 dbms_output.put_line ('---------------------');
18 dbms_output.put_line ('This is what you record in log table:');
19 dbms_output.put (dbms_utility.format_error_stack);
20 dbms_output.put (dbms_utility.format_error_backtrace);
21 dbms_output.put_line ('---------------------');
22 raise;
23 end;
24 /
---------------------
This is what you record in log table:
ORA-01476: divisor is equal to zero
ORA-06512: at line 7
ORA-06512: at line 11
ORA-06512: at line 14
---------------------
declare
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 22
Related
I want to know if there is any way in oracle to redirect the control from exception block to the current begin/end block.
Following is the code snippet. i variable loops through the table data sets, I want to update table i; if there is any exception go to the exec1 exception block,do the stuff and then try updating table i again.
If control goes to the exec2 exception then continue to the next iteration.
I want help as after performing steps in exec1 exception, how can I try updating table(i) again; so that if it fails again it is handled in exec1 exception block.
FOR i in c_tables
LOOP
BEGIN
---label---
Update (i);
DBMS_OUTPUT.PUT_LINE ('ROWS UPDATED IS: ' || SQL%ROWCOUNT);
EXCEPTION
WHEN exec1 THEN
BEGIN
do_stuff();
goto label;
EXCEPTION WHEN exec2 THEN
do_stuff();
continue;
END;
END;
END LOOP;
I tried to simulate what you have, in Scott's schema.
It seems that you'd be good if you moved the label in front of the BEGIN, not after it. Have a look:
SQL> DECLARE
2 l_deptno_dflt NUMBER := 50;
3 BEGIN
4 FOR i IN (SELECT distinct deptno FROM emp)
5 LOOP
6 <<this_is_label>> -- put label here ...
7 BEGIN
8 -- <<this_is_label>> -- ... not here
9 UPDATE emp
10 SET deptno = l_deptno_dflt
11 WHERE deptno = i.deptno;
12
13 DBMS_OUTPUT.put_line ('rows updated is ' || SQL%ROWCOUNT);
14 EXCEPTION
15 WHEN OTHERS
16 THEN
17 DBMS_OUTPUT.put_line ('this is exec1');
18
19 BEGIN
20 l_deptno_dflt := 40;
21 goto this_is_label;
22 END;
23 END;
24 END LOOP;
25 END;
26 /
this is exec1
rows updated is 6
rows updated is 5
rows updated is 3
PL/SQL procedure successfully completed.
SQL>
If it is where you initially put it, it raises the mentioned PLS-00375 error.
Environment: Oracle 11g
I have a type ty1 with some arguments like s1, s2.
If I use it like this:
SELECT ty1(s1,s2) BULK COLLECT INTO l_collection_of_ty1 FROM ...
I get a collection of ty1.
Now if there is an exception raised inside one of the constructor calls of ty1 the corresponding element of my collection is set to NULL, but the overall SELECT works (no exception, collection returned).
My question, can I detect this right after the SELECT without having to loop over the collection? Is there maybe even a way to access the original error message in a similar way as SQL%BULK_EXCEPTION does for DML?
One workaround I thought of is to not use the constructor during BULK COLLECT, but read collections of s1 and s2, then construct the TYPE in my own loop where I can handle the exception, but that is much more code and I would prefer it if Oracle has some build in way.
here is a testcase that demonstrates that all exceptions are thrown through a bulk-select, but NO_DATA_FOUND is dropped.
-- remember to retreive dbms_output
SET SERVEROUTPUT ON
CREATE OR REPLACE FUNCTION p1 (v1 PLS_INTEGER)
RETURN NUMBER
AS
BEGIN
CASE v1
WHEN 1
THEN
RAISE ACCESS_INTO_NULL;
WHEN 2
THEN
RAISE CASE_NOT_FOUND;
WHEN 3
THEN
RAISE COLLECTION_IS_NULL;
WHEN 4
THEN
RAISE CURSOR_ALREADY_OPEN;
WHEN 5
THEN
RAISE DUP_VAL_ON_INDEX;
WHEN 6
THEN
RAISE INVALID_CURSOR;
WHEN 7
THEN
RAISE INVALID_NUMBER;
WHEN 8
THEN
RAISE LOGIN_DENIED;
WHEN 9
THEN
RAISE NO_DATA_FOUND;
WHEN 10
THEN
RAISE NOT_LOGGED_ON;
WHEN 11
THEN
RAISE PROGRAM_ERROR;
WHEN 12
THEN
RAISE ROWTYPE_MISMATCH;
WHEN 13
THEN
RAISE SELF_IS_NULL;
WHEN 14
THEN
RAISE STORAGE_ERROR;
WHEN 15
THEN
RAISE SUBSCRIPT_BEYOND_COUNT;
WHEN 16
THEN
RAISE SUBSCRIPT_OUTSIDE_LIMIT;
WHEN 17
THEN
RAISE SYS_INVALID_ROWID;
WHEN 18
THEN
RAISE TIMEOUT_ON_RESOURCE;
WHEN 19
THEN
RAISE TOO_MANY_ROWS;
WHEN 20
THEN
RAISE VALUE_ERROR;
WHEN 21
THEN
RAISE ZERO_DIVIDE;
ELSE
RETURN v1;
END CASE;
END;
/
DECLARE
TYPE type1 IS TABLE OF NUMBER;
col1 type1;
BEGIN
FOR ii IN 1 .. 22
LOOP
BEGIN
SELECT p1 (ii)
BULK COLLECT INTO col1
FROM DUAL;
IF col1 (1) IS NULL
THEN
DBMS_OUTPUT.put_line (TO_CHAR (ii, '00') || ': NULL');
ELSE
DBMS_OUTPUT.put_line (TO_CHAR (ii, '00') || ': ' || col1 (1));
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (
TO_CHAR (ii, '00')
|| ': exception '
|| SQLCODE);
END;
END LOOP;
END;
/
I wrote a small test case for you and the exception has to be swallowed inside ty1 and not raised, because otherwise the select would not finish successfully:
create or replace function p1 (v1 number) return number
as
begin
if v1 = 1 then
return 1;
elsif v1 = 2 then
raise_application_error(-20010,'err 2');
else
raise_application_error(-20010,'err 3');
end if;
end;
/
declare
type type1 is table of number;
col1 type1;
begin
select p1(level) bulk collect into col1 from dual connect by level <=3;
end;
/
Result:
Error report -
ORA-20010: err 2
So my suggestion to you is - if you want to stay close to your solution - that at the place where you handle the exception in ty1, you write the exceptions to a table. You can then access this table to find the exceptions and don't need to loop through the whole collection. But honestly, what's wrong with looping in PL/SQL over a collection, it's all in memory? HTH
I see only two options.
An exception is consumed inside constructor.
The another constructor is in use.
Example:
create or replace type ty1 as object (p1 number
, constructor function ty1 (p1 varchar2)
return self as result);
create or replace type body ty1
is
constructor function ty1 (p1 varchar2)
return self as result is
x number;
begin
raise_application_error(-20000,'Always Exception');
return;
end;
end;
Test 1 no exception:
declare
type l_collection_of_ty1 is table of ty1;
a varchar2(4000);
x l_collection_of_ty1;
begin
--test1 select ty1(level) bulk collect into x from dual connect by level < 10;
-- no exceptions
--test2 select ty1(level||1) bulk collect into x from dual connect by level < 10;
-- exceptions
--test3 select ty1(null) bulk collect into x from dual connect by level < 10;
-- exceptions
end;
In Test1 db is using Attribute-Value Constructor. It is generate by default.
In Test2 db is using user defined constructor.
In Test3 db is not able to find out which constructor should be used.()
Write a PL/SQL procedure: when somebody enters a number, it'll print that number. Otherwise, it'll display error message.
You could also try to convert your input string to a number, and then catch the potential conversion error
Create Or Replace Procedure is_number(p_num varchar2) Is
v_num Number;
Begin
v_num := to_number(p_num);
dbms_output.put_line(v_num);
Exception
When VALUE_ERROR Then
dbms_output.put_line(p_num || ' is not a number');
End is_number;
Not too smart, but will get you started.
Using regular expressions (REGEXP_LIKE), check whether value is a number, consisting of
any number of digits [0-9]+
optionally |
followed by a decimal point . (backslash is here to escape it, as dot represents any character in a regular expression)
followed by any number of digits [0-9]+
^ and $ are anchors for beginning and end of entered value (i.e. it must begin and end with a digit)
Here it is:
SQL> create or replace procedure p_test (par_input in varchar2)
2 is
3 begin
4 if regexp_like(par_input, '^[0-9]+|(\.[0-9]+)$') then
5 dbms_output.put_line(par_input);
6 else
7 dbms_output.put_line('Error');
8 end if;
9 end;
10 /
Procedure created.
SQL> set serveroutput on;
SQL>
SQL> begin
2 p_test('2');
3 p_test('2.13');
4 p_test('x');
5 p_test('&#');
6 end;
7 /
2
2.13
Error
Error
PL/SQL procedure successfully completed.
SQL>
You can do it like this,
set serveroutput on;
BEGIN
DBMS_OUTPUT.PUT_LINE('&number'+0);
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Error');
END;
/
Trigger code -
CREATE OR REPLACE TRIGGER tib_ms_merchant_group
BEFORE INSERT
ON ms_merchant_group FOR EACH ROW DECLARE integrity_error EXCEPTION;
PRAGMA EXCEPTION_INIT (integrity_error, -20001);
errno INTEGER;
errmsg CHAR(200);
dummy INTEGER;
FOUND BOOLEAN;
BEGIN
-- Errors handling
EXCEPTION
WHEN integrity_error THEN
raise_application_error(errno, errmsg);
END;
Error -
"PLS-00103: Encountered the symbol "EXCEPTION" when expecting one of the following:
:= . ( # % ;
The symbol ";" was substituted for "EXCEPTION" to continue."
Probably you should declare some content after the BEGIN directive to make the function do something actual? This is (probably) demanded by the SQL syntax. That is why EXCEPTION cant be put straight after BEGIN
As #sanfor said, you need to have something between the BEGIN-EXCEPTION block. I could compile the trigger -
SQL> CREATE
2 OR
3 replace TRIGGER tib_ms_merchant_group BEFORE
4 INSERT
5 ON emp FOR EACH ROW DECLARE integrity_error EXCEPTION;
6
7 PRAGMA EXCEPTION_INIT (integrity_error, -20001);
8 errno INTEGER;
9 errmsg CHAR(200);
10 dummy INTEGER;
11 FOUND BOOLEAN;
12 BEGIN
13 NULL;
14 EXCEPTION
15 WHEN integrity_error THEN
16 raise_application_error(errno, errmsg);
17 END;
18 /
Trigger created.
SQL> sho err
No errors.
SQL>
Just replace the table name and add the required trigger logic after BEGIN.
I have a stored procedure as follows
procedure Save_FormField(name in varchar2,age in varchar2,returnval out varchar2)
begin
update STATEMENT
if SQL%ROWCOUNT>0 then
returnval :='1';
end;
it throws
ORA-06502: PL/SQL: numeric or value error:
character string buffer too smallORA-06512:
at
returnval :='1';
is it wrong?
Have a look at the following test case :
SQL> CREATE OR REPLACE
2 PROCEDURE Save_FormField(
3 name IN VARCHAR2,
4 RETURNVAL OUT VARCHAR2)
5 AS
6 BEGIN
7 UPDATE EMP1 SET ENAME = 'Hello' WHERE ENAME = name;
8 IF SQL%ROWCOUNT>0 THEN
9 RETURNVAL :='1';
10 END IF;
11 END;
12 /
Procedure created.
SQL>
SQL> declare
2 ret varchar2(100);
3 a varchar2(1);
4 BEGIN
5 Save_FormField('SCOTT',ret);
6 a:= ret;
7 dbms_output.put_line(a);
8 END;
9 /
1
PL/SQL procedure successfully completed.
It definitely looks like that this error is thrown directly in the update statement.
You should check the length of the columns in your table and the length of the values you are trying to update.
Also be carefull with the return value (returnval).
If the update statement doesn't update any record, it is null.
You might want to consider an else-block to set another value in this case.
I know it is a little bit late, but I see there is no answer, so maybe this helps other people.
If you are calling that procedure with using ODP.NET, then you just have to set length of the out parameter.
An example:
cmd.Parameters.Add("returnval", OracleDbType.Varchar2, 500, "", ParameterDirection.Output);
Here 500 is the length of out parameter. Hope, it helps.