I have a pl sql procedure which accepts array of elements and inserts them into a table.
At the beginning of the procedure i am deleting data from backup table and inserting data from main table into backup table. Then i am deleting data from main table and looping through the arguments to the proc and inserting records. When i face dup_val_on_index exception, the rollback is happening to start point of the proc. I mean the exception block is getting executed. But the rollback is not happening.
For example, if i insert 2 rows which has duplicate values, dup_val_on_index exception has to be raised and 1st row should not be inserted.
Below is my code. If any exception happening inside the loop, i want to rollback the insert as well and delete operation performed at the beginning of the procedure
PROCEDURE insert_sales_data (
p_depot_code IN depotcode_array,
p_depot_name IN depotname_array,
p_dell_split IN dellsplit_array,
p_sector IN sector_array,
p_locality IN locality_array,
p_tnt_depot_code IN tntdepotcode_array,
p_postal_code IN postalcode_array,
p_primary_sort IN primarysort_array,
p_secondary_sort IN secondarysort_array,
p_user IN VARCHAR2,
p_error_message OUT VARCHAR2,
p_count OUT NUMBER
)
IS
BEGIN
SAVEPOINT s1;
DELETE FROM sales_backup;
INSERT INTO sales_backup
SELECT
*
FROM
sales;
DELETE FROM sales;
FOR i IN p_sector.first..p_sector.last LOOP
BEGIN
INSERT INTO sales (
depot_code,
depot_name,
dell_split,
sector,
locality,
tnt_depot_code,
postal_code,
primary_sort,
secondary_sort,
create_date,
create_user_id,
uuid
) VALUES (
p_depot_code(i),
p_depot_name(i),
p_dell_split(i),
p_sector(i),
p_locality(i),
p_tnt_depot_code(i),
p_postal_code(i),
p_primary_sort(i),
p_secondary_sort(i),
SYSDATE,
p_user,
sys_guid()
);
EXCEPTION
WHEN dup_val_on_index THEN
ROLLBACK TO s1;
EXIT;
WHEN OTHERS THEN
ROLLBACK TO s1;
EXIT;
END;
END LOOP;
SELECT
COUNT(*)
INTO p_count
FROM
uk_depots;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO s1;
END;
Hoping, I understood your question correctly. Please try to use below block.
PROCEDURE insert_sales_data (
p_depot_code IN depotcode_array,
p_depot_name IN depotname_array,
p_dell_split IN dellsplit_array,
p_sector IN sector_array,
p_locality IN locality_array,
p_tnt_depot_code IN tntdepotcode_array,
p_postal_code IN postalcode_array,
p_primary_sort IN primarysort_array,
p_secondary_sort IN secondarysort_array,
p_user IN VARCHAR2,
p_error_message OUT VARCHAR2,
p_count OUT NUMBER
)
IS
BEGIN
DELETE FROM sales_backup;
INSERT INTO sales_backup
SELECT
*
FROM
sales;
DELETE FROM sales;
FOR i IN p_sector.first..p_sector.last LOOP
BEGIN
INSERT INTO sales (
depot_code,
depot_name,
dell_split,
sector,
locality,
tnt_depot_code,
postal_code,
primary_sort,
secondary_sort,
create_date,
create_user_id,
uuid
) VALUES (
p_depot_code(i),
p_depot_name(i),
p_dell_split(i),
p_sector(i),
p_locality(i),
p_tnt_depot_code(i),
p_postal_code(i),
p_primary_sort(i),
p_secondary_sort(i),
SYSDATE,
p_user,
sys_guid()
);
EXCEPTION
WHEN dup_val_on_index THEN
ROLLBACK;
EXIT;
WHEN OTHERS THEN
ROLLBACK;
EXIT;
END;
END LOOP;
SELECT
COUNT(*)
INTO p_count
FROM
uk_depots;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END;
From how I understood your question it should be simpler:
PROCEDURE insert_sales_data (
p_depot_code IN depotcode_array,
p_depot_name IN depotname_array,
p_dell_split IN dellsplit_array,
p_sector IN sector_array,
p_locality IN locality_array,
p_tnt_depot_code IN tntdepotcode_array,
p_postal_code IN postalcode_array,
p_primary_sort IN primarysort_array,
p_secondary_sort IN secondarysort_array,
p_user IN VARCHAR2,
p_error_message OUT VARCHAR2,
p_count OUT NUMBER
)
IS
BEGIN
SAVEPOINT s1;
DELETE FROM sales_backup;
INSERT INTO sales_backup
SELECT
*
FROM
sales;
DELETE FROM sales;
FOR i IN p_sector.first..p_sector.last LOOP
INSERT INTO sales (
depot_code,
depot_name,
dell_split,
sector,
locality,
tnt_depot_code,
postal_code,
primary_sort,
secondary_sort,
create_date,
create_user_id,
uuid
) VALUES (
p_depot_code(i),
p_depot_name(i),
p_dell_split(i),
p_sector(i),
p_locality(i),
p_tnt_depot_code(i),
p_postal_code(i),
p_primary_sort(i),
p_secondary_sort(i),
SYSDATE,
p_user,
sys_guid()
);
END LOOP;
SELECT
COUNT(*)
INTO p_count
FROM
uk_depots;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO s1;
END;
This block is fairly useless, you perform the rollback no matter which error you get.
EXCEPTION
WHEN dup_val_on_index THEN
ROLLBACK TO s1;
EXIT;
WHEN OTHERS THEN
ROLLBACK TO s1;
EXIT;
END;
DECLARE
ERROR_COUNT NUMBER;
errno number;
e_msg varchar2(50);
e_idx varchar2(20);
TYPE emp_type IS TABLE OF emp_source%ROWTYPE;
EMP_VAR emp_type;
CURSOR c1 IS SELECT * FROM emp_source;
BEGIN
OPEN c1;
loop
FETCH c1 BULK COLLECT INTO EMP_VAR;
BEGIN
FORALL i in 1 .. EMP_VAR.COUNT save exceptions
insert INTO emp_target (e_id,e_name,sal) values (EMP_VAR(i).E_ID,EMP_VAR(i).E_NAME,EMP_VAR(i).SAL);
FORALL i in 1 .. EMP_VAR.COUNT save exceptions
insert INTO department_target (dep_name) values (EMP_VAR(i).dep_name);
EXCEPTION
WHEN others THEN
ERROR_COUNT := sql%bulk_exceptions.count;
for i in 1 .. ERROR_COUNT
loop
errno := sql%bulk_exceptions(i).error_code;
e_msg := sqlerrm(-errno);
e_idx := sql%bulk_exceptions(i).error_index;
insert into emp_save_exc values(errno,e_msg,e_idx);
end loop;
END;
exit when c1%notfound;
end loop;
close c1;
commit;
END;
You can put a BEGIN/END block around the first FORALL and handle the exception like this:
BEGIN
FORALL i in 1 .. EMP_VAR.COUNT save exceptions
insert INTO emp_target (e_id,e_name,sal) values (EMP_VAR(i).E_ID,EMP_VAR(i).E_NAME,EMP_VAR(i).SAL);
EXCEPTION
WHEN ...
END;
You can also turn each block into a procedure to make your code more structured:
DECLARE
...
PROCEDURE insert_emp_target (p_emp_var emp_type) IS
BEGIN
FORALL i in 1 .. EMP_VAR.COUNT save exceptions
insert INTO emp_target (e_id,e_name,sal)
values (EMP_VAR(i).E_ID,EMP_VAR(i).E_NAME,EMP_VAR(i).SAL);
EXCEPTION
WHEN ...
END;
PROCEDURE insert_dept_target (p_emp_var emp_type) IS
...
BEGIN
OPEN c1;
loop
FETCH c1 BULK COLLECT INTO EMP_VAR;
exit when c1%notfound;
insert_emp_target (emp_var);
insert_dept_target (emp_var);
...
end loop;
close c1;
commit;
END;
I have a procedure below, which loops through a cursor does some logic. I have put FOR UPDATE NOWAIT on the cursor to lock my records set, in case someone from another session wants to update the same set.
But there may be a scenario where the records I have selected in my cursor, is locked by someone as well, in which case, I want to simply log the locked record in a log table and continue to the next record in the loop.
PROCEDURE test(p_id IN NUMBER)
IS
CURSOR cur_test IS
SELECT emp_id,
emp_name
FROM
EMP
FOR UPDATE NOWAIT;
row_locked EXCEPTION;
PRAGMA EXCEPTION_INIT(row_locked, -54);
BEGIN
FOR r_cur_test IN cur_test
LOOP
BEGIN
--do something
EXCEPTION
WHEN row_locked THEN
--log locked record in log table
log_lock('Record is locked');
COMMIT;
END;
END LOOP;
--call log function to log a successful run
COMMIT;
EXCEPTION
WHEN OTHERS THEN
--Unsuccessful completion of the run.Trap all unhandled exceptions.
--log error in error table
log_lock('Process exited with error');
ROLLBACK;
END;
for testing, I open session 1 and executed
select * from EMP FOR UPDATE NOWAIT
and in session 2, I executed
begin
test(1);
end;
it always went to the OTHERS exception, as a result of which, the procecure just terminated without continuing looping through the cursor.
Can anyone give me some advice? Thanks a lot
When you do open cursor for update this statement works in scenario: at first run select query and set lock for all records and after then do fetch operations.
PROCEDURE test(p_id IN NUMBER)
IS
CURSOR cur_test IS
SELECT emp_id,
emp_name,
rowid as row_id
FROM
EMP;
row_locked EXCEPTION;
PRAGMA EXCEPTION_INIT(row_locked, -54);
v_sql varchar2(4000) := 'SELECT 1 FROM EMP t WHERE rowid = :row_id FOR UPDATE NOWAIT';
c int;
n int;
BEGIN
c := dbms_sql.open_cursor;
dbms_sql.parse(c, v_sql, dbms_sql.native);
FOR r_cur_test IN cur_test
LOOP
BEGIN
dbms_sql.bind_variable (c, 'row_id', i.row_id);
n := dbms_sql.execute(c);
--do something
EXCEPTION
WHEN row_locked THEN
--log locked record in log table
log_lock('Record is locked');
COMMIT;
END;
END LOOP;
dbms_sql.close_cursor(c);
--call log function to log a successful run
COMMIT;
EXCEPTION
WHEN OTHERS THEN
--Unsuccessful completion of the run.Trap all unhandled exceptions.
--log error in error table
log_lock('Process exited with error');
dbms_sql.close_cursor(c);
ROLLBACK;
END;
I want to use the execute immediate command, to insert collection below PROCEDURE
CREATE OR REPLACE PROCEDURE P_LOAD_HLD(
p_app_name IN VARCHAR2,
p_filename IN VARCHAR2,
p_rectype IN VARCHAR2,
p_tabname IN VARCHAR2,
o_status OUT Number)
IS
type t_tab is table of holding_template%rowtype;
v_tab t_tab;
process_size number := 10000;
dummy_cursor SYS_REFCURSOR;
BEGIN
o_status := 0;
null;
if p_rectype='my_test' THEN
OPEN dummy_cursor FOR
select *
from TAB_staging a
where a.record_type = p_filename;
end if;
LOOP
BEGIN
FETCH dummy_cursor BULK COLLECT INTO V_TAB LIMIT PROCESS_SIZE;
FORALL I IN V_TAB.FIRST .. V_TAB.LAST
execute immediate 'insert into '||p_tabname||' values v_tab(i))';
EXCEPTION WHEN OTHERS THEN
FOR I IN 1 .. V_TAB.COUNT
LOOP
BEGIN
EXECUTE IMMEDIATE ' INSERT INTO '||p_tabname || ' VALUES ( '||
'v_tab(i).k0,v_tab(i).k1,v_tab(i).k2,v_tab(i).k3,v_tab(i).k4';
COMMIT;
EXCEPTION WHEN OTHERS THEN
RAISE;
END;
END LOOP;
END;
END LOOP;
END P_LOAD_HLD ;
this gives me the error message.
[Error] PLS-00435 (742: 7): PLS-00435: DML statement without BULK
In-BIND cannot be used inside FORALL
I want to update the same record which fires a trigger. I have done that using "BEFORE INSERT"
option. But note that I have use a transaction to rollback the operation if there is an any faliure.
CREATE OR REPLACE TRIGGER GANUKA.INTF_CONTROLLER_UPLOADER
BEFORE insert ON GANUKA.INTF_CONTROLLER for each row
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
max_id INTEGER;
stat VARCHAR2(32);
begin
select :new.id into max_id from dual;
select :new.status into stat from dual;
IF STAT = 'NEW' THEN --ONLY NEW UPLOADS WILL CONTINUE FOR PROCESS
:NEW.STATUS := 'STARTED';
max_id := GANUKA.BACKOFFICE_UPDATE(max_id); --PL/SQL function
:NEW.STATUS := 'COMPLETED';
ELSE
:NEW.STATUS := 'ABORTED';
:NEW.REMARKS :='STATUS IS NOT RECONGNIZED';
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
ROLLBACK;
RAISE;
end;
/
Problem is if there is an an any exception I want to update the record to set the state as 'Failed'. Can any one tell me how to do that.
I'm not sure why do you use autonomous transaction here and why do you have to commit/rollback in the trigger...
Does this do it?
CREATE OR REPLACE TRIGGER GANUKA.INTF_CONTROLLER_UPLOADER
BEFORE insert ON GANUKA.INTF_CONTROLLER for each row
DECLARE
max_id INTEGER;
stat VARCHAR2(32);
begin
max_id := :new.id;
stat := :new.status;
IF STAT = 'NEW' THEN --ONLY NEW UPLOADS WILL CONTINUE FOR PROCESS
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
max_id := GANUKA.BACKOFFICE_UPDATE(max_id); --PL/SQL function
COMMIT;
:NEW.STATUS := 'COMPLETED';
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
:new.status := 'FAILED';
END;
ELSE
:NEW.STATUS := 'ABORTED';
:NEW.REMARKS :='STATUS IS NOT RECONGNIZED';
END IF;
end;
/