handling excpetions in plsql block while running in a loop - oracle

I have a requirement where I need to drop partitions for more than one tables in a loop.If for some reason that partition doesnt exist in a table the whole procedure is giving error . But I want to drop other partititions which exists in other tables without coming out of the loop

Use an inner begin-exception-end block ("inner" meaning "within a loop"). Something like this:
begin
for cur_r in (select whatever from ...) loop
-- inner block begins here
begin
do stuff here
exception
when ... then ...
end;
-- inner block ends here
end loop;
end;

Related

where do i use no data found exception on FOR statement inside a stored procedure

i have the next question:
Im not very good at DBs, i've been requested to add a "No data found exception" to a stored procedure.
This is the SP:
CREATE OR REPLACE PROCEDURE TABLE."SP_UPD"
(
PERROR OUT VARCHAR2
)
AS
BEGIN
FOR TMP_TABLE IN
(SELECT FIELDS FROM TABLES)
--I need to verify HERE if the for returns--
--no values, because once the loop starts it automaticaly updates tables--
LOOP
BEGIN
CODE
MORE CODE
END
END LOOP;
I added the exception here, at the bottom before the last backslash, is this right?:
EXCEPTION WHEN NO_DATA_FOUND
THEN
perror:='error message';
return;
END;
/
You don't have to do anything, because - in a cursor FOR loop (which is what you have), Oracle will skip everything between LOOP and END LOOP if cursor doesn't return any rows.
So: if select fields from tables doesn't return anything , code and more code won't ever be executed.

Oracle Procedure- Statements outside of End Loop not Executing

Need help with below. statements after end loop not executing. Structure is as follows:
Create or replace procedure a.xyz (b in varchar2,c in varchar2.....) is
bunch of variable declaration
cursor c1
begin
open c1;
loop
fetch c1 into ....;
exit when c1%notfound;
insert
insert
merge
merge
commit;
end loop;
insert
select into
send email
exception
end;
insert, select into, send email not getting executed. Any leads?
You didn't post interesting parts of the procedure - what EXCEPTION does?
This is your pseudocode, modified. Read comments I wrote.
Create or replace procedure a.xyz (b in varchar2,c in varchar2.....) is
bunch of variable declaration
cursor c1
begin
open c1;
loop
begin --> inner begin - exception - end block
fetch c1 into ....;
exit when c1%notfound;
insert
insert
merge
merge
exception
when ... then ... --> handle exceptions you expect. If you used
-- WHEN OTHERS THEN NULL, that'a usually a huge mistake.
-- Never use unless you're testing something,
-- without RAISE, or if you really don't care if
-- something went wrong
end; --> end of inner block
end loop;
insert
select into
send email
commit; --> commit should be out of the loop; even better,
-- you should let the CALLER to decide when and
-- whether to commit, not the procedure itself
exception
when ... then ...
end;
Inner BEGIN-EXCEPTION-END block - if exceptions are properly handled - will let the LOOP end its execution. You should log the error you got (for testing purposes, it could be even
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(c1.id ||': '|| sqlerrm);
END;
so that you'd actually see what went wrong. If it were just
EXCEPTION
WHEN OTHERS THEN NULL;
END;
you have no idea whether some error happened, where nor why.

PLSQL IMPLICIT CURSOR No Data Found After CURSOR

I have a Main cursor that is working fine.
declare
v_firm_id number;
amount number;
v_total_sum TABLE_TEMP.TOTAL_SUM%TYPE;
CURSOR MT_CURSOR IS
SELECT firm_id FROM t_firm;
BEGIN
OPEN MT_CURSOR;
LOOP
FETCH MT_CURSOR INTO v_firm_id;
EXIT WHEN MT_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(to_char(sysdate, 'mi:ss') ||'--- '|| v_firm_id)
INSERT INTO TABLE_TEMP(TOTAL_SUM) VALUES(v_firm_id)
COMMIT;
END LOOP;
DBMS_LOCK.SLEEP(20);
BEGIN
FOR loop_emp IN
(SELECT TOTAL_SUM INTO v_total_sum FROM TABLE_TEMP)
LOOP
dbms_output.put_line(to_char(sysdate, 'mi:ss') ||'--- '|| v_total_sum || '-TEST--');
END LOOP loop_emp;
END;
end;
Everything Works fine except dbms_output.put_line(v_total_sum || '---');
I do not get any data there. I get the correct number of rows. which it inserted.
The problem is the cursor FOR loop has a redundant into clause which it appears the compiler silently ignores, and so v_total_sum is never used.
Try this:
begin
for r in (
select firm_id from t_firm
)
loop
insert into table_temp (total_sum) values (r.firm_id);
end loop;
dbms_lock.sleep(20);
for r in (
select total_sum from table_temp
)
loop
dbms_output.put_line(r.total_sum || '---');
end loop;
commit;
end;
If this had been a stored procedure rather than an anonymous block and you had PL/SQL compiler warnings enabled with alter session set plsql_warnings = 'ENABLE:ALL'; (or the equivalent preference setting in your IDE) then you would have seen:
PLW-05016: INTO clause should not be specified here
I also moved the commit to the end so you only commit once.
To summarise the comments below, the Cursor FOR loop construction declares, opens, fetches and closes the cursor for you, and is potentially faster because it fetches in batches of 100 (or similar - I haven't tested in recent versions). Simpler code has less chance of bugs and is easier to maintain in the future, for example if you need to add a column to the cursor.
Note the original version had:
for loop_emp in (...)
loop
...
end loop loop_emp;
This is misleading because loop_emp is the name of the record, not the cursor or the loop. The compiler is ignoring the text after end loop although really it should at least warn you. If you wanted to name the loop, you would use a label like <<LOOP_EMP>> above it. (I always name my loop records r, similar to the i you often see used in numeric loops.)

In Oracle, how to catch exception when table used for the loop doesn't exists

I have this code:
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
END;
My problem is that sometimes SOME_USER.SOMETABLE do not exists but I want the rest of the script to be run. I know that checking if the table exists before running the code (in a IF ... THEN block) will not work because SELECT * FROM SOME_USER.SOME_TABLE is evaluated at compile time.
So another avenue is to run the SELECT with EXECUTE IMMEDIATE. This way it will be evaluated at run time and I would be able to catch the exception. Unfortunately I can't find a way to use EXECUTE IMMEDIATE with my U1 IN loop. How I should achieve this?
I'm on Oracle 11g and the SQL script is run from a batch script on Windows.
You can use the 'OPEN FOR' syntax:
DECLARE
CUR SYS_REFCURSOR;
<variables or record type> -- declare as appropriate
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO <variables or record type>;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
END;
/
You need to fetch each row into variables or a record type, you can't use %ROWTYPE as the table still won't exist; and you can change to do bulk fetches if that's appropriate for your data volumes.
If you run that you'll still get ORA-00942, but if this is in a stored program you won't get it until run time, and you can now add an IF block to check for the table's existence before the OPEN.
Having a data model where objects may or may not exist at run-time seems rather fishy though...
Proposed solution with cursor is fine, I would add an exception handling for this particular exception : Table or view does not exist ORA-00942.
DECLARE
e_missing_t EXCEPTION;
pragma exception_init (e_missing_t,-942);
something number; --some variable you need to fetch to
CUR SYS_REFCURSOR;
BEGIN
OPEN CUR FOR 'SELECT * FROM SOME_USER.SOME_TABLE';
LOOP
FETCH CUR INTO something;
EXIT WHEN CUR%NOTFOUND;
-- do something with variables or record
END LOOP;
CLOSE CUR;
EXCEPTION
WHEN e_missing_t THEN
dbms_output.put_line('some_table is missing');
END;
/
You could possibly use a workaround -
Create a nested table type and store the results of the SELECT in it. Use that type to loop through values.
So,
SELECT data_obj(COL1, COL2) bulk collect into data_tbl_typ from data_table;
This part can go in the dynamic sql. (Remember to use bind variables)
And then just loop through this nested table type in your procedure.
Use the DBMS_SQL package to run the query.
Follow the examples in this Oracle documentation:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#sthref6147
pl/sql has the exception clause for that. OTHERS catches pretty much everything. You can deal with the exception in the function, or print a message and pass it back to the main. Break your function up into smaller functions and have each one catch it's own exception.
BEGIN
FOR
U1 IN (SELECT * FROM SOME_USER.SOME_TABLE)
LOOP
BEGIN
-- do something;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Oh well. The table isn't there.');
--RAISE;
END;

Logging the erronous ID but not breaking the LOOP

Here is my problem. I am looping through some values and some of those values raise an exception. I want to log those values, but the program flow should not break. I mean, if I encounter such a value, I will simply log the error and skip to the next value.
here is the simplified version :
drop table test;
--destination Table
create table test
(
id varchar2(2)
);
-- Error log table
create table test_log
(
id varchar2(10)
);
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
INSERT INTO test
VALUES (c);
l_num:=c;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
COMMIT;
--raise;
END;
/
My problem is, when it's encountering an error, it simply jumps to the exception section and not looping through the later values in the loop.
You can catch the exception in an inner block:
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
l_num:=c;
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
END; -- inner block
END LOOP;
END;
/
The loop won't be interrupted if an exception occurs; only that single insert is affected. Note that you don't really want to commit inside the exception handler as that will commit all the successful inserts so far, not just the error. (If you wanted to log the errors but later roll back you could use an autonomous procedure to do the logging, but that's a separate discussion). And you don't want to re-raise the exception as that would still break the loop and the whole outer anonymous block.
Catching 'others' is generally not a good idea; if you have known errors you could encounter - like a bad data or number format - it's better to catch those explicitly. If you have a wider problem like not being able to extend a data file then the insert inside the exception handler would presumably fail anyway though.
You don't really need l_num any more as c is still in-scope for the inner exception handler, so you could simplify slightly to:
BEGIN
FOR c IN 90..102
LOOP
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(c);
INSERT INTO test_log
VALUES (c);
END; -- inner block
END LOOP;
END;
/
You can also use FORALL.
That answer to your request with SAVE EXCEPTION statement because the FORALL loop to continue even if some DML operations fail. And it will be more efficient than a simple LOOP.
Ex :
DECLARE
CURSOR LCUR$VAL IS
SELECT ID
FROM test1;
TYPE LT$TAB IS TABLE OF TEST%ROWTYPE;
LA$TAB_TEST LT$TAB;
dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
LC$ERRORS NUMBER(11);
LC$ERRNO NUMBER(11);
LC$MSG VARCHAR2(4000 CHAR);
LC$IDX NUMBER(11);
BEGIN
OPEN LCUR$VAL;
LOOP
FETCH LCUR$VAL BULK COLLECT INTO LA$TAB_TEST LIMIT 1000;
BEGIN
FORALL x IN 1 .. LA$TAB_TEST.COUNT SAVE EXCEPTIONS
INSERT INTO test VALUES LA$TAB_TEST(x);
EXIT WHEN LCUR$VAL%NOTFOUND;
EXCEPTION
WHEN DML_ERRORS THEN
LC$ERRORS := sql%BULK_EXCEPTIONS.COUNT;
FOR idx IN 1 .. LC$ERRORS
LOOP
LC$ERRNO := sql%BULK_EXCEPTIONS (idx).ERROR_CODE;
LC$MSG := sqlerrm(-LC$ERRNO);
LC$IDX := sql%BULK_EXCEPTIONS(idx).error_index;
-- here you can log in table : test_log...
END LOOP;
END;
END LOOP;
CLOSE LCUR$VAL;
END;
Hoping that it can help.

Resources