How to determine row/value throwing error in PL/SQL statement? - oracle

(Oracle PL/SQL)
If I have a simple SQL statement that is throwing an error, ie:
DECLARE
v_sql_errm varchar2(2048);
BEGIN
UPDATE my_table SET my_column = do_something(my_column)
WHERE my_column IS NOT NULL;
EXCEPTION
when others then
-- How can I obtain the row/value causing the error (unknown)?
v_sql_errm := SQLERRM;
insert into log_error (msg) values ('Error updating value (unknown): '||
v_sql_errm);
END;
Is there any way within the exception block to determine the row/value on which the query is encountering an error? I would like to be able to log it so that I can then go in and modify/correct the specific data value causing the error.

This can be done using DML error logging, if you are on 10gR2 or later.
An example:
SQL> create table my_table (my_column)
2 as
3 select level from dual connect by level <= 9
4 /
Tabel is aangemaakt.
SQL> create function do_something
2 ( p_my_column in my_table.my_column%type
3 ) return my_table.my_column%type
4 is
5 begin
6 return 10 + p_my_column;
7 end;
8 /
Functie is aangemaakt.
SQL> alter table my_table add check (my_column not in (12,14))
2 /
Tabel is gewijzigd.
SQL> exec dbms_errlog.create_error_log('my_table')
PL/SQL-procedure is geslaagd.
This creates an error logging table called err$_my_table. This table is filled by adding a log errors clause to your update statement:
SQL> begin
2 update my_table
3 set my_column = do_something(my_column)
4 where my_column is not null
5 log errors reject limit unlimited
6 ;
7 end;
8 /
PL/SQL-procedure is geslaagd.
SQL> select * from err$_my_table
2 /
ORA_ERR_NUMBER$
--------------------------------------
ORA_ERR_MESG$
--------------------------------------------------------------------
ORA_ERR_ROWID$
--------------------------------------------------------------------
OR
--
ORA_ERR_TAG$
--------------------------------------------------------------------
MY_COLUMN
--------------------------------------------------------------------
2290
ORA-02290: check constraint (RWK.SYS_C00110133) violated
AAGY/aAAQAABevcAAB
U
12
2290
ORA-02290: check constraint (RWK.SYS_C00110133) violated
AAGY/aAAQAABevcAAD
U
14
2 rijen zijn geselecteerd.
Prior to 10gR2, you can use the SAVE EXCEPTIONS clause: http://rwijk.blogspot.com/2007/11/save-exceptions.html

A solution using the SAVE EXCEPTIONS clause:
SQL> create table my_table (my_column)
2 as
3 select level from dual connect by level <= 9
4 /
Table created.
SQL> create function do_something
2 ( p_my_column in my_table.my_column%type
3 ) return my_table.my_column%type
4 is
5 begin
6 return 10 + p_my_column;
7 end;
8 /
Function created.
SQL> alter table my_table add check (my_column not in (12,14))
2 /
Table altered.
SQL> declare
2 e_forall_error exception;
3 pragma exception_init(e_forall_error,-24381)
4 ;
5 type t_my_columns is table of my_table.my_column%type;
6 a_my_columns t_my_columns := t_my_columns()
7 ;
8 begin
9 select my_column
10 bulk collect into a_my_columns
11 from my_table
12 ;
13 forall i in 1..a_my_columns.count save exceptions
14 update my_table
15 set my_column = do_something(a_my_columns(i))
16 where my_column = a_my_columns(i)
17 ;
18 exception
19 when e_forall_error then
20 for i in 1..sql%bulk_exceptions.count
21 loop
22 dbms_output.put_line(a_my_columns(sql%bulk_exceptions(i).error_index));
23 end loop;
24 end;
25 /
2
4
PL/SQL procedure successfully completed.
For very large data sets, you probably don't want to blow up your PGA memory, so be sure to use the LIMIT clause in that case.

try outputting your error and see if it gives you the information you are looking for. For example:
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;

For more detailing information regarding how execution arrived at the line in question, you could try displaying the output returned by these functions:
DBMS_UTILITY.format_error_stack:
Format the current error stack. This can be used in exception handlers to look at the full error stack.
DBMS_UTILITY.format_error_backtrace:
Format the backtrace from the point of the current error to the exception handler where the error has been caught. NULL string is returned if no error is currently being handled.

Try this (not tested):
DECLARE
cursor c1 is
select key_column, my_column
from my_table
WHERE my_column IS NOT NULL
ORDER BY key_column;
my_table_rec my_table%ROWTYPE;
BEGIN
FOR my_table_rec in c1
LOOP
UPDATE my_table SET my_column = do_something(my_column)
WHERE key_column = my_table_rec.key_column;
END LOOP;
EXCEPTION
when others then
insert into log_error (msg) values ('Error updating key_column: ' || my_table_rec.key_column || ', my_column: ' || my_table_rec.my_column);
END;

PL/SQL defines 2 global variables to refer to errors:
SQLERRM : SQL error Message
SQLERRNO: SQL error Number
This is readable in the EXCEPTION block in your PL/SQL.
DECLARE
x number;
BEGIN
SELECT 5/0 INTO x FROM DUAL;
EXCEPTION
WHEN OTHERS THEN:
dbms_output.put_line('Error Message: '||SQLERRM);
dbms_output.put_line('Error Number: '||SQLERRNO);
END;

Related

Create trigger that does the same as unique static constraint PL/SQL

As a disclaimer, this question is only for my curiosity and for practicing triggers and compound triggers particularly.
I've been trying to replace the UNIQUE constraint with a trigger in order to understand triggers more, but I haven't been successful so far, mainly because of the global variables that I'm not so comfortable with inside the compound triggers.
what I'm trying to do with a trigger :
ALTER TABLE Employee
ADD CONSTRAINT emp_UQ
UNIQUE (id_emp, id_office);
here's what I tried so far (t for type, g for global):
CREATE OR REPLACE TRIGGER BIUUniqueConstraint
FOR INSERT OR UPDATE ON Employee
COMPOUND TRIGGER
TYPE tIdEmpOffice IS TABLE OF Employee.id_emp%TYPE
INDEX BY VARCHAR2(80);
gIdEmpOffice tIdEmpOffice;
TYPE tId_emp IS TABLE OF Employee.id_emp%TYPE;
gId_emp tId_emp;
TYPE tId_office IS TABLE OF Employee.id_office%TYPE;
gId_office tId_office;
BEFORE STATEMENT IS
BEGIN
SELECT e.id_emp, e.id_office
BULK COLLECT INTO gId_emp, gId_office
FROM Employee e
ORDER BY e.id_emp;
FOR j IN 1..gId_emp.COUNT() LOOP
gIdEmpOffice(gId_emp(j)) := gId_office(j);
END LOOP;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(gIdEmpOffice(:NEW.id_emp);
END IF;
END BEFORE EACH ROW;
END BIUCompteParti;
/
I have no clue how to move forward with this trigger and would like suggestions and explanations if possible about how to use globale variables to store data and how to use them on a row level.
Here's one option.
Sample table:
SQL> create table employee (id_emp number, id_office number);
Table created.
Trigger:
SQL> create or replace trigger trg_emp_unique
2 for insert or update on employee
3 compound trigger
4 type t_row is record (id_emp number, id_office number);
5 type t_tab is table of t_row;
6 l_tab t_tab := t_tab();
7 l_cnt number;
8
9 before each row is
10 begin
11 l_tab.extend;
12 l_tab(l_tab.last).id_emp := :new.id_emp;
13 l_tab(l_tab.last).id_office := :new.id_office;
14 end before each row;
15
16 after statement is
17 begin
18 for i in l_tab.first .. l_tab.last loop
19 select count(*) into l_cnt
20 from employee
21 where id_emp = l_tab(i).id_emp
22 and id_office = l_tab(i).id_office;
23 if l_cnt > 1 then
24 raise_application_error(-20000, 'Unique constraint violated');
25 end if;
26 end loop;
27 l_tab.delete;
28 end after statement;
29 end trg_emp_unique;
30 /
Trigger created.
SQL>
As you can see, it doesn't contain before statement nor after each row parts; if they aren't used, you don't have to put them into the trigger.
Let's try it:
SQL> insert into employee(id_emp, id_office) values (1, 1);
1 row created.
SQL> insert into employee(id_emp, id_office) values (1, 2);
1 row created.
Re-insert the first combination (1, 1):
SQL> insert into employee(id_emp, id_office) values (1, 1);
insert into employee(id_emp, id_office) values (1, 1)
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
That failed (as it should). Let's update existing rows:
SQL> select * from employee;
ID_EMP ID_OFFICE
---------- ----------
1 1
1 2
SQL> update employee set id_office = 5;
update employee set id_office = 5
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
Kind of works.
You can count whether a paired value for those columns already exists with a statement level trigger such as
CREATE OR REPLACE TRIGGER trg_chk_unique_emp_dept_id
AFTER INSERT ON employee
DECLARE
val INT;
BEGIN
SELECT NVL(MAX(COUNT(*)),0)
INTO val
FROM employee
GROUP BY id_emp, id_office;
IF val > 1 THEN
RAISE_APPLICATION_ERROR(-20304,
'Each employee may be assigned to a department once at most !');
END IF;
END;
/
which will check if an attempt made to insert the same value pairs more than once, and if so, it will hurl with an error message.
Demo

Trigger created with compilation errors. PL/SQL

I've been searching for a while how to resolve this issue and couldn't find anything helpful.
Why this trigger is created with compilation errors?
The warning im getting
Warning: Trigger created with compilation errors.
CREATE OR REPLACE TRIGGER peope_in_dept
AFTER INSERT OR DELETE OR UPDATE on emp
FOR EACH ROW
BEGIN
VARIABLE v NUMBER;
EXECUTE :v := count_people_in_dept(:new.deptno)
IF :v <= 0 or :v >= 10 THEN
raise_application_error(-20000,'Nieodpowiednia liczba pracownikow');
END IF;
END;
/
Syntax errors. Should be
SQL> CREATE OR REPLACE TRIGGER peope_in_dept
2 AFTER INSERT OR DELETE OR UPDATE
3 ON emp
4 FOR EACH ROW
5 DECLARE
6 v NUMBER;
7 BEGIN
8 v := count_people_in_dept (:new.deptno);
9
10 IF v <= 0
11 OR v >= 10
12 THEN
13 raise_application_error (-20000, 'Nieodpowiednia liczba pracownikow');
14 END IF;
15 END;
16 /
Trigger created.
SQL>
However, I suspect it won't work if count_people_in_dept function counts employees in emp table. Why? Because table is mutating:
SQL> UPDATE emp SET comm = 1 WHERE deptno = 10;
UPDATE emp SET comm = 1 WHERE deptno = 10
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.COUNT_PEOPLE_IN_DEPT", line 6
ORA-06512: at "SCOTT.PEOPE_IN_DEPT", line 4
ORA-04088: error during execution of trigger 'SCOTT.PEOPE_IN_DEPT'
SQL>
Now, it depends on what you really are doing in that function. One way out is to set the function to be an autonomous transaction; another one is to use compound trigger; the third option is to use custom type and a package. Or, modify the process altogether (i.e. avoid the trigger).
Why this trigger is created with compilation errors?
Because you are trying to create a SQL/Plus global bind variable inside a PL/SQL procedure and then using EXECUTE rather than just calling the function (and you missed a ; statement terminator).
The correct syntax would be:
FOR EACH ROW
DECLARE
v NUMBER;
BEGIN
v := count_people_in_dept(:new.deptno);
IF v <= 0 or v >= 10 THEN
Then it would compile. However, it still won't work as the table would be mutating as the trigger is running.
Instead, you can use a statement trigger rather than a row trigger:
CREATE OR REPLACE TRIGGER people_in_dept
AFTER INSERT OR DELETE OR UPDATE on emp
DECLARE
v_count PLS_INTEGER;
BEGIN
SELECT 1
INTO v_count
FROM dept d
LEFT OUTER JOIN emp e
ON (e.deptno = d.deptno)
GROUP BY d.deptno
HAVING COUNT(e.deptno) NOT BETWEEN 1 AND 9
FETCH FIRST ROW ONLY;
raise_application_error(-20000,'Nieodpowiednia liczba pracownikow');
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Do nothing.
NULL;
END;
/
db<>fiddle here
You could also write it as a compound trigger that aggregates the changes in deptno and then only checks the changed values using your function; however, that is a lot more complicated.

ora-06575 package or function is in an invalid state pl sql

I am new to pl sql and i am trying to call a procedure but i am getting the error ora-06575 package or function is in an invalid state.
What do you think i am doing wrong? Thanks
select * from NB_tfr_USERS; --previous table created
create or replace procedure nb_tfr_add_user
(
i_user_name in nb_tfr_users.name%type,
i_address in nb_tfr_users.address%type,
i_birthdate in nb_tfr_users.birthdate%type,
o_userid out nb_tfr_users.id%type,
o_errm out varchar2
) AS
--Variables
l_user_id number;
--Parameters invalid exception
error_params exception;
BEGIN
--validate parameters
--- name can not be empty
--- birthdate can be empty
if i_user_name is null or length(i_user_name) = 0 then
raise error_params;
end if;
--initialize variables
l_user_id := nb_seq_tfr_user_id.nextval;
--insert new user
insert into NB_tfr_USERS (id, name, address, birthdate)
values (l_user_id, i_user_name, i_address, i_birthdate);
--deal with exceptions
exception
when error_params then
o_errm := 'Invalid Parameters!';
dbms_output.put_line(o_errm);
when others then
dbms_output.put_line('Error: '||sqlerrm);
o_errm:=substr(1,150,sqlerrm);
raise;
END NB_tfr_ADD_USER;
call
nb_tfr_add_user(54,'tf','lx',12121989);
So when i try to call the procedure i get the error.
Well, it looks OK - apart from the fact that you incorrectly called the procedure.
Here's a test case.
SQL> create table nb_tfr_users
2 (id number, name varchar2(10), address varchar2(10), birthdate date);
Table created.
SQL> create sequence nb_seq_tfr_user_id;
Sequence created.
The procedure:
SQL> create or replace procedure nb_tfr_add_user
2 (
3 i_user_name in nb_tfr_users.name%type,
4 i_address in nb_tfr_users.address%type,
5 i_birthdate in nb_tfr_users.birthdate%type,
6 o_userid out nb_tfr_users.id%type,
7 o_errm out varchar2
8 ) AS
9 --Variables
10 l_user_id number;
11 --Parameters invalid exception
12 error_params exception;
13 BEGIN
14 --validate parameters
15 --- name can not be empty
16 --- birthdate can be empty
17 if i_user_name is null or length(i_user_name) = 0 then
18 raise error_params;
19 end if;
20
21 --initialize variables
22 l_user_id := nb_seq_tfr_user_id.nextval;
23
24 --insert new user
25 insert into NB_tfr_USERS (id, name, address, birthdate)
26 values (l_user_id, i_user_name, i_address, i_birthdate);
27
28 --deal with exceptions
29 exception
30 when error_params then
31 o_errm := 'Invalid Parameters!';
32 dbms_output.put_line(o_errm);
33
34 when others then
35 dbms_output.put_line('Error: '||sqlerrm);
36 o_errm:=substr(1,150,sqlerrm);
37 raise;
38 END NB_tfr_ADD_USER;
39 /
Procedure created.
SQL>
Testing - it shows how you should have called it. As there are 3 IN and 2 OUT parameters, that's what you should have done as well.
SQL> set serveroutput on
SQL> declare
2 l_userid nb_tfr_users.id%type;
3 l_Errm varchar2(200);
4 begin
5 nb_tfr_add_user(i_user_name => 'tf',
6 i_address => 'lx',
7 i_birthdate => sysdate,
8 o_userid => l_userid,
9 o_errm => l_errm);
10 dbms_output.put_Line(l_userid ||' '||l_errm);
11 end;
12 /
PL/SQL procedure successfully completed.
SQL> select * from nb_tfr_users;
ID NAME ADDRESS BIRTHDAT
---------- ---------- ---------- --------
1 tf lx 09.09.19
SQL>
If you got the error, it - obviously - means that there's something wrong. I don't know what, but - maybe you forgot to create the sequence.
Anyway: after creating stored procedures, check whether everything is OK by running (in SQL*Plus) show err, e.g.
SQL> create or replace procedure p_test is
2 begin
3 insert into abc (id) values (seq_does_not_exist.nextval);
4 end;
5 /
Warning: Procedure created with compilation errors.
SQL> show err
Errors for PROCEDURE P_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
3/3 PL/SQL: SQL Statement ignored
3/15 PL/SQL: ORA-00942: table or view does not exist
SQL>
Or, query user_source:
SQL> select line, text, attribute from user_errors where name = 'P_TEST';
LINE TEXT ATTRIBUTE
----- -------------------------------------------------- ---------
3 PL/SQL: ORA-00942: table or view does not exist ERROR
3 PL/SQL: SQL Statement ignored ERROR
SQL>
I suggest you do the same and see what's wrong with the procedure.

PL/SQL error reference to uninitialised collection error even when its initialised

I have a PL/SQL script which used nested table. Below is the sample code.
type rec is record
(
--col data types here
)
type rec_table is table of rec;
v_rec_table rec_table := rec_table(); --initialising here.
arr_size integer := 0; --edit 1
...
...
begin
...
...
open cursor;
loop
fetch cursor bulk collect into v_rec_table limit arr_size; --edit
if nvl(v_rec_table.last,0) > 0 --it shows error is here.
then
...
...
end if;
The error is ORA-06531: Reference to uninitialized collection. Please help me debug this one.
If would help if you posted complete (sample) code (instead of "...").
Here's an example based on Scott's schema which works OK (your error line is line 14):
SQL> declare
2 type rec is record (id number);
3 type rec_table is table of rec;
4 v_rec_table rec_table := rec_table();
5
6 cursor c1 is select empno from emp;
7 begin
8 open c1;
9 loop
10 exit when c1%notfound;
11 fetch c1 bulk collect into v_rec_table;
12 end loop;
13 dbms_output.put_line('last = ' || v_rec_table.last);
14 if nvl(v_rec_table.last, 0) > 0 then
15 dbms_output.put_line('last exists');
16 end if;
17 close c1;
18 end;
19 /
last = 12
last exists
PL/SQL procedure successfully completed.
Though, I'm not sure what's that LOOP used for, as you could have done it as
SQL> declare
2 type rec is record (id number);
3 type rec_table is table of rec;
4 v_rec_table rec_table := rec_table();
5 begin
6 select empno bulk collect into v_rec_table from emp;
7 dbms_output.put_line('last = ' || v_rec_table.last);
8 end;
9 /
last = 12
PL/SQL procedure successfully completed.
Although your problem is not reproducible, there are few things I found could be improved in your code.
If you are using a bulk collect, the collection is initialised automatically and hence, this line is not required
v_rec_table rec_table := rec_table();
Also, as #Littlefoot mentioned, LOOP is not required unless you are using the LIMIT clause.
You may use the COUNT collection function instead of nvl(v_rec_table.last,0)
v_rec_table.count
Instead of defining a record type and bulk collecting into it, you may define a collection of cursor%ROWTYPE;
CURSOR cur is SELECT column1,column2 FROM tablename;
type rec_table is table of cur%ROWTYPE;
v_rec_table rec_table;
Apologies since i did not post whole code. The error occurred because i did not add index by to two columns in the record definition.
After reading through few articles i found my flaw.

ORACLE use custom tableObject into a function

I want insert a cursor into my custom tableObject, but it is not always found.
My RECORD:
create or replace type "RECORDRANKING" as object
(
-- Attributes
COL1 NUMBER,
COL2 VARCHAR(50),
COL3 NUMBER
-- Member functions and procedures
-- member procedure <ProcedureName>(<Parameter> <Datatype>)
)
This is object table:
CREATE OR REPLACE TYPE "TBRANKING" AS TABLE OF RECORDRANKING;
Now I go into creating a function (into a package):
CREATE OR REPLACE PACKAGE BODY PKLBOTTONI as
FUNCTION testlb
(
p_gapup IN NUMBER,
p_gadown IN NUMBER
)
RETURN SYS_REFCURSOR IS
cursor_ranking SYS_REFCURSOR;
position NUMBER ;
gap_ranking TBRANKING;
gaprecord RECORDRANKING;
upgap NUMBER;
downgap NUMBER;
BEGIN
select * into cursor_ranking from(
select pkranking.getRanking( 100 ) from dual);
LOOP
FETCH cursor_ranking INTO gap_ranking;
EXIT WHEN cursor_ranking%NOTFOUND;
INSERT INTO gap_ranking (COL1,COL2,COL3)
VALUES
(cursor_ranking.C1,
cursor_ranking.C2,
cursor_ranking.C3);
END LOOP;
return gap_ranking;
END;
END PKLBOTTONI;
I always get:
Compilation errors for PACKAGE BODY PKLBOTTONI
Error: PL/SQL: ORA-00942: table or view does not exist
Line: 32
Text: INSERT INTO gap_ranking
In the loop you're both fetching into "gap_ranking" and then trying to "insert into" it again. Insert into a collection is not a valid syntax. Fetch is ok, either in a loop or using bulk collect to fetch multiple records at once.
From your excerpt it doesn't look like you have a physical database table, so the way to do it in PL/SQL would be something like below.
For more help using collections you can check Oracle docs below too:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/collections.htm
SQL> set serveroutput on
SQL> declare
2 gap_ranking tbranking := tbranking(null); -- initialize
3 begin
4 gap_ranking.delete; -- clear empty record
5 for cur in
6 (select level as i from dual connect by level <= 5)
7 loop
8 -- insert empty
9 gap_ranking.extend;
10 -- attribute values
11 gap_ranking(gap_ranking.last) := recordranking(1000 + cur.i, 'TEST' || cur.i, 10 + cur.i);
12 end loop;
13 -- loop to print - just to illustrate
14 for j in gap_ranking.first .. gap_ranking.last
15 loop
16 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
17 gap_ranking(j).col2 || ',' ||
18 gap_ranking(j).col3);
19
20 end loop;
21 -- same as...
22 for j in 1 .. gap_ranking.count
23 loop
24 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
25 gap_ranking(j).col2 || ',' ||
26 gap_ranking(j).col3);
27
28 end loop;
29 end;
30 /
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
PL/SQL procedure successfully completed
SQL>

Resources