Getting failed id's from for all save exceptions - sql

I am trying to update salary of employees using forall. Whenever any error occurs while updating I need to save for which employee id error has occurred.
But it gives following error while compiling
Error(14,24): PLS-00201: identifier 'INDX' must be declared
Below is my code
PROCEDURE PROC1 (V_EMP_ID DBMS_SQL.NUMBER_TABLE)
IS
lv_error_string VARCHAR2(4000);
BEGIN
FORALL INDX IN V_EMP_ID.FIRST..V_EMP_ID.LAST SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'UPDATE EMPLOYEES SET SALARY=SALARY+10000 WHERE EMP_ID=:1'
USING V_EMP_ID(INDX);
EXCEPTION
WHEN OTHERS
THEN
FOR J IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
lv_error_string:=lv_error_string
||sqlerrm (-sql%bulk_exceptions(j).error_code)
|| ' for'||V_EMP_ID(INDX);
END LOOP;
END;

Use this: The error is that in exception block you are trying to access a loop variable that is being used in begin block.
So your || ' for'||V_EMP_ID(INDX); should be || ' for'||V_EMP_ID(J);
CREATE OR REPLACE PROCEDURE PROC1 (V_EMP_ID DBMS_SQL.NUMBER_TABLE)
IS
lv_error_string VARCHAR2(4000);
BEGIN
FORALL INDX IN V_EMP_ID.FIRST..V_EMP_ID.LAST SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'UPDATE EMPLOYEES SET SALARY=SALARY+10000 WHERE EMP_ID=:1'
USING V_EMP_ID(INDX);
EXCEPTION
WHEN OTHERS
THEN
FOR J IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
lv_error_string:=lv_error_string
||sqlerrm (-sql%bulk_exceptions(j).error_code)
|| ' for'||V_EMP_ID(J);
END LOOP;
END;
Not sure why you use Execute Immediate when you can easily do as below:
CREATE OR REPLACE PROCEDURE PROC1 (V_EMP_ID DBMS_SQL.NUMBER_TABLE)
IS
lv_error_string VARCHAR2(4000);
BEGIN
FORALL INDX IN V_EMP_ID.FIRST..V_EMP_ID.LAST SAVE EXCEPTIONS
UPDATE EMPLOYEES
SET SALARY=SALARY+10000
WHERE EMP_ID= V_EMP_ID(INDX);
EXCEPTION
WHEN OTHERS
THEN
FOR J IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
lv_error_string:=lv_error_string
||sqlerrm (-sql%bulk_exceptions(j).error_code)
|| ' for'||V_EMP_ID(J);
END LOOP;
END;

I would suggest to go with a single DML statement. And yes DML error loggins is possible.Hope this helps
--Creating a error log table
BEGIN
DBMS_ERRLOG.create_error_log (dml_table_name => 'EMPLOYEES');
END;
/
--ERR$_EMPLOYEES --> Errro table created
--Insertion with erroreous record
UPDATE EMPLOYEES
SET SALARY = SALARY + 10000
where EMP_ID in (<EMP_ID COLLECTION array
OR simple EMP_IDs>) LOG ERRORS
INTO ERR$_EMPLOYEES ('UPDATE') REJECT LIMIT UNLIMITED;
--Error will be logged into ERR$_EMPLOYEES table

Related

[PLSQL]How to know the exact row which raise SAVE EXCEPTION in BULK COLLECT

I'm trying to update rows from a bulk collect with limit using forall statement. However I couldn't get the exact row which raise the exception.
-- Cursor which get the rows to update
-- Creation of the table TAB_REQ
BEGIN
OPEN c_REQ;
LOOP
FETCH c_REQ BULK COLLECT INTO TAB_REQ LIMIT 50000;
BEGIN
FORALL ii in 1 .. TAB_REQ.count SAVE EXCEPTIONS
-- Update statement where exception will be raise at id=164588
EXCEPTION
WHEN OTHERS
THEN
l_errors := sql%BULK_EXCEPTIONS.count;
FOR i IN 1 .. l_errors
LOOP
l_errno := sql%BULK_EXCEPTIONS(i).ERROR_CODE;
l_msg := sqlerrm(-l_errno);
l_idx := sql%BULK_EXCEPTIONS(i).ERROR_INDEX;
DBMS_OUTPUT.PUT_LINE(l_errno||' : '||l_msg||' - '||l_idx||', id_ext: '|| TAB_REQ(i).id_ext);
END LOOP;
END;
exit when TAB_REQ.COUNT =0;
END LOOP;
CLOSE c_REQ;
The result is that the id printed out in exception does not correspond to the id that raise error in update loop, and the number of rows updated has 50000 less, while is exactly the limit number in bulk collect.
Anyone knows the raison?
You need to use sql%bulk_exceptions. You can see an example here. Keep in mind the errors are generic (meaning for errors such as "cannot insert null into X" you won't get the column name when using sqlerrm.).
To catch the errors you must define the exception before the execution block (or at the package level), then handle it:
DECLARE
e_forall EXCEPTION;
PRAGMA EXCEPTION_INIT (e_forall, -24381);
BEGIN
... bulk insert code that generates the exception ...
EXCEPTION
WHEN e_forall THEN
FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line ('Error '
|| ' on index '
|| SQL%BULK_EXCEPTIONS(i).ERROR_INDEX
|| SQLERRM (-1 * SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
END LOOP;
END;
/
What does the code generate on your side that prevents you from seeing the error/cause?
UPDATE:
OK, now that you updated the code + gave the output I think I found it.
The problem is here: TAB_REQ(i).id_ext
Try: TAB_REQ(l_idx).id_ext
ERROR_INDEX obtained from the array index is the actual record id, not the array index. So using i on the record will always return a wrong record. You need the index recovered from i.

How to execute from select result (oracle sql)

I created table with grants list. How I can execute grants from this table ?
Something like
select * from grants_table;
then EXECUTE IMMEDIATE result from select
You could write a loop
begin
for grant in (select * from grants_table)
loop
execute immediate grants.column_with_ddl;
end loop;
end;
Most likely, you'll want to do some amount of logging/ exception handling/ etc.
If your table is of considerable size you could take advantage of the bulk operations and binds, something among the lines:
DECLARE
TYPE cursor_ref IS REF CURSOR;
c1 cursor_ref;
TYPE grants_t IS TABLE OF grants%ROWTYPE;
grants_tab grants_t;
rows_fetched NUMBER;
errors NUMBER;
dml_errors EXCEPTION;
PRAGMA exception_init(dml_errors, -24345);
BEGIN
OPEN c1 FOR 'SELECT * FROM grants';
FETCH c1 BULK COLLECT INTO grants_tab;
rows_fetched := c1%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE('Number of grants: ' || TO_CHAR(rows_fetched));
BEGIN
FORALL i IN 1 .. grants_tab.count
EXECUTE IMMEDIATE '< some ddl> :1' USING grants_tab(i);
EXCEPTION WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
DBMS_OUTPUT.PUT_LINE('Number of errors is ' || errors);
FOR j IN 1..errors LOOP
DBMS_OUTPUT.PUT_LINE('Error ' || j || ' occurred on iteration ' || SQL%BULK_EXCEPTIONS(j).ERROR_INDEX);
DBMS_OUTPUT.PUT_LINE('Oracle error is ' || SQLERRM(-SQL%BULK_EXCEPTIONS(j).ERROR_CODE));
END LOOP;
END;
END;
/

Create a generic PL SQL procedure to log Bulk Collect errors dynamically

I recently learned about the use of BULK COLLECT in SQL. I found a way to handle exceptions generated by DML statements :
SET SERVEROUTPUT ON SIZE 99999;
--
DECLARE
--
bulk_errors exception;
PRAGMA exception_init(bulk_errors, -24381);
--
--
CURSOR cursEmployee IS
SELECT EMPLOYEE_ID, EMPLOYEE_NAME
FROM EMPLOYEE;
TYPE employee_table IS TABLE OF cursEmployee%rowtype;
employee_rec employee_table;
--
BEGIN
--
OPEN cursEmployee;
FETCH cursEmployee BULK COLLECT INTO employee_rec LIMIT 10000;
--
WHILE employee_rec.COUNT != 0 LOOP
--
FORALL indx IN INDICES OF employee_rec save exceptions
--
INSERT INTO EMPLOYEE (
EMPLOYEE_ID,
EMPLOYEE_NAME
)
VALUES (
employee_rec.EMPLOYEE_ID,
employee_rec.EMPLOYEE_NAME
);
--
COMMIT;
--
FETCH cursEmployee BULK COLLECT INTO employee_rec LIMIT 10000;
--
END LOOP;
exception when bulk_errors then
for i in 1 .. sql%bulk_exceptions.COUNT loop
dbms_output.put_line('Employee Id : ' || sql%bulk_exceptions(i).EMPLOYEE_ID);
dbms_output.put_line('Employee Id : ' || sql%bulk_exceptions(i).EMPLOYEE_NAME);
dbms_output.put_line('Error Message: '||sqlerrm(-sql%bulk_exceptions(i).error_code));
end loop;
CLOSE cursEmployee;
END;
/
I then created a generic procedure to log the exceptions :
CREATE OR REPLACE PROCEDURE LOG_BULK_EXCEPTIONS IS
BEGIN
IF SQL%BULK_EXCEPTIONS.COUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE(' Errors occured during a BULK COLLECT statement : ');
DBMS_OUTPUT.PUT_LINE(' Number of exceptions : ' || SQL%BULK_EXCEPTIONS.COUNT );
--
FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(' Error : ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
DBMS_OUTPUT.PUT_LINE('Backtrace : ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END LOOP;
--
END IF;
END;
/
I find this way of logging exceptions a bit limited : we just get the error (numeric value, cannot insert null into, etc...). I'm looking for a way to add information about the data / specific elements in the cursor that raised the error.
To do this, I need to pass a column name as parameter to my procedure, and concatenate it to obtain this sort of statement :
dbms_output.put_line(' Internal Id : ' || sql%bulk_exceptions(i).MY_COLUMN_PARAMETER);
This way, I could use this logging procedure everywhere in the Database, which would be great.
Does anyone know how to concatenate a string parameter to this " sql%bulk_exceptions(i). " and execute it correctly ?
Yes, you can get what you are looking for provided what you want to add in in the original collection. The sql%bulk_exceptions collection has another column, ERROR_INDEX. It contains the index of the row in the original collection. This allows you to reference values from the that collection via
employee_rec(sql%bulk_exceptions(i).error_index).id;
employee_rec(sql%bulk_exceptions(i).error_index).name;
Your procedure has another issue. Your exception block is outside the you loop processing the bulk collection. As a consequence your bulk buffers will be processed only until the first buffer contains an error; no subsequent buffers will be processed. You can avoid this by creating a block inside the processing loop and handling exceptions within the inner block. Also, nice to see you went to the effort to actually close the cursor. However, it is in the exception block so it only executes if there is a exception. See here for example of each. Since I did not want to create over 10000 rows for demo I reduced the Bulk Limit to 3.

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;

Trigger calling procedure error

I am having some troubles with this trigger. I created a procedure to check and see if salary is within a certain boundary. If it fails to fall within a certain range, raise the exception. The problem is even though the procedure compiles with no errors, the trigger can not find the procedure.
set serveroutput on;
create or replace procedure check_salary (
tmp_id in varchar2,
tmp_sal in number
)
IS
v_sal number(6,0) := tmp_sal;
v_min number(6,0);
v_max number(6,0);
ex_fail exception;
cursor cur_select is
select min_salary, job_id, max_salary
from jobs where job_id = tmp_id;
BEGIN
for rec_something in cur_select loop
v_min := rec_something.min_salary;
v_max := rec_something.max_salary;
if v_sal >= v_min and v_sal <= v_max then
raise ex_fail;
end if;
end loop;
exception
when ex_fail then
dbms_output.put_line('Invalid salary ' || v_sal || ' must be between ' || v_min || ' and ' || v_max ||'.');
END;
/
show errors;
create or replace trigger check_salary_trg
after insert or update on employees
for each row
declare
begin
IF UPDATING or INSERTING THEN
execute check_salary(:NEW.job_id, :NEW.salary);
end if;
end;
/
show errors;
The Error Message:
PROCEDURE check_salary compiled
No Errors.
TRIGGER check_salary_trg compiled
Warning: execution completed with warning
5/13 PLS-00103: Encountered the symbol "CHECK_SALARY" when expecting one of the following:
:= . ( # % ; immediate
The symbol ":=" was substituted for "CHECK_SALARY" to continue.
Change it to:
create or replace trigger check_salary_trg
after insert or update on employees
for each row
begin
IF UPDATING or INSERTING THEN
check_salary(:NEW.job_id, :NEW.salary);
end if;
end;
/
When you are executing a procedure within a PL/SQL block, you do not use the
EXECUTE syntax
More information about execute you can check the below link
http://docstore.mik.ua/orelly/oracle/prog2/ch23_01.htm
The stack overflow exception is due to the use of dbms_output.put_line inside check_salary procedure.
SQL*Plus command set serveroutput on reserves little size as default, you must specify the buffer size or remove the dbms_output.put_line from check_salary procedure.
In order to increase default buffer size use this:
set serveroutput on size 1000000