when no_data_found stop the current iteration and resume the next - oracle

I have a code like below:
DECLARE
rec emp%ROWTYPE
BEGIN
<<start_again>>
FOR rec IN c1 LOOP
SELECT emp_index
INTO flag
FROM m_sme006
WHERE emp_id = rec.emp_id
AND eff_dt = rec.eff_dt
AND end_dt = rec.end_dt
AND last_maint_ts > rec.last_maint_ts;
END LOOP;
EXCEPTION
WHEN no_data_found THEN
GOTO start_again;
END;
Every time the no_data_found exception occurs, I want the execution to go back and continue with next iteration. But it throws the error PLS-00375: illegal GOTO statement
Also can i include this line dbms_output.Put_line('Outside exception block'rec.sme006_index); in the exception block to see where this happened?
Thanks in Advance

You can't return the program flow from the exception block back to the execution block. You need to put the exception block inside the loop, like this:
DECLARE
rec emp%ROWTYPE
BEGIN
FOR rec IN c1 LOOP
BEGIN
SELECT emp_index
INTO flag
FROM m_sme006
WHERE emp_id = rec.emp_id
AND eff_dt = rec.eff_dt
AND end_dt = rec.end_dt
AND last_maint_ts > rec.last_maint_ts;
EXCEPTION
WHEN no_data_found THEN
dbms_output.Put_line('Outside exception block ' || rec.sme006_index);
END;
END LOOP;
END;

I believe you are looking for something like this:
DECLARE
rec emp%ROWTYPE
BEGIN
FOR rec IN c1 LOOP
BEGIN
SELECT emp_index
INTO flag
FROM m_sme006
WHERE emp_id = rec.emp_id
AND eff_dt = rec.eff_dt
AND end_dt = rec.end_dt
AND last_maint_ts > rec.last_maint_ts;
EXCEPTION
WHEN no_data_found THEN
-- whatever code you want to log your error
END;
END LOOP;
END;

Related

Why does exception through me out of cursor loop?

I have a PL/SQL oracle procedure,
I run over list of sql_id to tune them.
I have an exception , but it through me out and finish.
Tried it with or without continue, but it's the same..
ANY IDEAS?
CREATE OR REPLACE Procedure sql_perf_for_sql_id
IS
v_sql_id VARCHAR2(13);
stmt_task VARCHAR2(64 CHAR);
retcode VARCHAR2(64 CHAR);
cursor c1 is
SELECT SQL_ID
FROM sql_for_tune
WHERE tuned='NO';
BEGIN
IF c1%ISOPEN THEN
CLOSE c1 ;
END IF;
open c1;
LOOP
--for r_c1 in c1 loop
BEGIN
fetch c1 into v_sql_id;
--v_sql_id := r_c1.sql_id;
exit when c1%notfound;
update sql_for_tune set remarks='RUNNING' where sql_id=v_sql_id;
commit;
stmt_task := NULL;
DBMS_OUTPUT.PUT_LINE (v_sql_id);
-- create a tuning task tune the statement
stmt_task := DBMS_SQLTUNE.CREATE_TUNING_TASK(sql_id => v_sql_id);
DBMS_OUTPUT.PUT_LINE( NVL(stmt_task,'Error CREATE_TUNING_TASK() Return Value'));
-- execute the resulting task
retcode := DBMS_SQLTUNE.EXECUTE_TUNING_TASK(stmt_task);
DBMS_OUTPUT.PUT_LINE( NVL(retcode,'Error EXECUTE_TUNING_TASK() Return Value'));
-- accept the resulting task
retcode := DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(stmt_task);
DBMS_OUTPUT.PUT_LINE( NVL(retcode,'Error ACCEPT_SQL_PROFILE() Return Value'));
-- Optional SELECT DBMS_SQLTUNE.report_tuning_task( stmt_task ) AS recommendations FROM dual;
update sql_for_tune set tuned='YES',
task_name = stmt_task ,
index_benefit=(SELECT max( r.benefit/100)
FROM dba_advisor_actions a,
dba_advisor_recommendations r
WHERE a.task_name=stmt_task
AND a.task_id = r.task_id
AND a.rec_id = r.rec_id
and a.command='CREATE INDEX'),
profile_benefit=(SELECT max( r.benefit/100)
FROM dba_advisor_actions a,
dba_advisor_recommendations r
WHERE a.task_name=stmt_task
AND a.task_id = r.task_id
AND a.rec_id = r.rec_id
and a.command='ACCEPT SQL PROFILE')
where sql_id=v_sql_id;
commit;
update sql_for_tune set remarks='END' where sql_id=v_sql_id;
commit;
EXCEPTION
WHEN OTHERS THEN
update sql_for_tune set tuned='CHECK', task_name = stmt_task where sql_id=v_sql_id;
commit;
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
--continue;
END;
END LOOP;
close c1;
END;
SQL> execute sql_perf_for_sql_id;
BEGIN sql_perf_for_sql_id; END;
*
ERROR at line 1:
ORA-20001: An error was encountered - -13786 -ERROR- ORA-13786: missing SQL
text of statement object "1" for tuning task "TASK_464272"
ORA-06512: at "SQL_PERF.SQL_PERF_FOR_SQL_ID", line 73
ORA-06512: at line 1
SQL>
Thank you
It exits because of
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
If you want the loop to continue, don't raise the error - log it using some other way (for example, dbms_output.put_line being the simplest; store it into a table by using a call of the autonomous transaction procedure).
Also, remove all those COMMITs out of the loop. Commit once, at the end of the procedure.

Trying to get the actual data that cause an exception

I have a procedure that takes an input of 2 Associative arrays and after some basic count checks, does a FORALL statement to insert the data into a table.
Here is the procedure:
PROCEDURE INSERT_RECS(P_PROD_TYP IN prod_type, P_PROD_ADD_PK IN prod_pk_type)
IS
uniq_key EXCEPTION;
PRAGMA EXCEPTION_INIT(uniq_key, -00001);
loc_cnt NUMBER;
BEGIN
IF P_PROD_TYP.COUNT = P_PROD_ADD_PK.COUNT THEN
FORALL i IN P_PROD_TYP.FIRST .. P_PROD_TYP.LAST
INSERT INTO product_table ( pk,
id,
created_by,
created_on,
last_chg_by,
last_chg_on)
VALUES (P_PROD_ADD_PK(i),
P_PROD_TYP(i).id,
P_PROD_TYP(i).created_by,
P_PROD_TYP(i).created_on,
NULL,
NULL);
END IF;
EXCEPTION
WHEN uniq_key THEN
loc_cnt := SQL%BULK_EXCEPTIONS.count;
FOR i IN 1 .. loc_cnt LOOP
dbms_output.put_line('EXCEPTION: Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index ||
' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE) ||
' SQLERRM: ' || SQLERRM ||
' SQLCODE: ' || SQLCODE ||
' stack: ' || SYS.dbms_utility.format_call_stack);
END LOOP;
RETURN;
END;
What I would like is if I hit an exception, is there a way that I could have view of the record that is causing the issue, essentially the index in the associative array or at least have the SQL% info have the info.
I have look at the following:
http://www.dba-oracle.com/plsql/t_plsql_exceptions.htm
but this outputs the info about the column but that is not what I am after.
Not sure if really answers your query but you can use the loop variable i in exception block to display the content of the exception array in your case. See below an example procedure:
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
---trying to rasie an exception by using a calculation
SET SALARY=SALARY * 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
WHERE ID_E= V_EMP_ID(INDX);
EXCEPTION
WHEN OTHERS
THEN
FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
---Am printing the value of the exception array.
dbms_output.put_line('exception Raised for record' ||V_EMP_ID(i));
END LOOP;
END;
/
Ouput:
SQL> DECLARE
empid DBMS_SQL.NUMBER_TABLE;
BEGIN
empid (1) := 1;
empid (2) := 9;
PROC1 (empid);
END;
/
exception Raised for record 1
PL/SQL procedure successfully completed.

Oracle PL/SQL - how do I run the same block of code for ALL exceptions?

Oracle 11g. This seems like it should be stupidly obvious, but I haven't seen an example. I have 2 exceptions which each need to write slightly different log messages, and then they should do the same UPDATE and CONTINUE.
Is there any way to structure the exception so I only need to type the UPDATE and CONTINUE statements once, while keeping the different logging?
FOR my_rec IN my_cursor
LOOP
BEGIN
...do some stuff
EXCEPTION
WHEN NO_DATA_FOUND THEN
log_detail.new('Skipping record - ID not found');
UPDATE my_table
SET operation_result = 'Failed'
WHERE my_id = my_rec.some_id;
CONTINUE;
WHEN OTHERS THEN
log_detail.new('Skipping record - unknown error');
UPDATE my_table
SET operation_result = 'Failed'
WHERE my_id = my_rec.some_id;
CONTINUE;
END;
END LOOP;
Did you try:
FOR my_rec IN my_cursor
LOOP
BEGIN
...do some stuff
EXCEPTION
WHEN OTHERS THEN
if sqlcode=-1403 then
log_detail.new('Skipping record - ID not found');
else
log_detail.new('Skipping record - unknown error');
end if;
UPDATE my_table
SET operation_result = 'Failed'
WHERE my_id = my_rec.some_id;
CONTINUE;
END;
END LOOP;
You could try:
DECLARE
vError VARCHAR2(1);
vMessage VARCHAR2(100);
BEGIN
FOR my_rec IN my_cursor LOOP
vError := 'N';
BEGIN
...do some stuff
EXCEPTION
WHEN NO_DATA_FOUND THEN
vError := 'S';
vMessage := 'Skipping record - ID not found';
WHEN OTHERS THEN
vError := 'S';
vMessage := 'Skipping record - unknown error';
END;
IF vError = 'S' THEN
log_detail.new(vMessage);
UPDATE my_table
SET operation_result = 'Failed'
WHERE my_id = my_rec.some_id;
CONTINUE;
END IF;
END LOOP;
END;

passing cursor to a function does not work always

I have the below procedure and the functions, I pass the cursor result as an argument to the function load_in_parallel. The issue is that sometimes the entries are processed and other time it doesn't. I really don't understand why it happens. Could it be due to the size of the result? Please see the PL/SQL procedure and function as below,
procedure COPY_REPORT_CONTENTS is
begin
begin
-- START PARALLEL processing from here
for rec in (
select /*+ parallel(5) */ * from table(load_in_parallel(CURSOR(
select gen_report_id, creation_ts, selection_criteria, content from stage_table stgrep where not exists(select 1 from content rep where rep.report_id = stgrep.gen_report_id) and nvl(stgrep.gen_report_id, 0) <> 0)))
)
LOOP
--some processing here
END LOOP;
exception
when others then
ROLLBACK;
raise;
end;
end;
function load_in_parallel (c_pis_a1 IN SYS_REFCURSOR)
return vdps_load_stats
PARALLEL_ENABLE (PARTITION c_pis_a1 BY ANY)
PIPELINED
IS
PRAGMA AUTONOMOUS_TRANSACTION;
begin
loop
begin
fetch c_pis_a1 into r_pis_a1;
exit when c_pis_a1%notfound;
--some processing
commit;
exception
when others then
rollback;
--move the records to failed table and continue with next records
commit;
end;
end loop;
close c_pis_a1;
PIPE ROW(v_vdps_load_stats);
RETURN;
end;
Any help will really be appreciated.
Note: included the missing exception block.

Is it possible to use sql%rowcount for SELECT?

The code below may return more than one row. Will sql%rowcount return the number of rows fetched?
select * from emp where empname = 'Justin' and dept='IT'
if sql%rowcount>0
...
This is my sample proc; am I using sql%rowcount in correct way?
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2,outInststatus OUT VARCHAR2,outSockid IN NUMBER,outport OUT VARCHAR2,outIP OUT VARCHAR2,outretvalue OUT NUMBER)
AS
BEGIN
select INST_STATUS into outInststatus from TINST_child where INST_ID = in_Hid and INST_STATUS = 'Y';
if outInststatus = 'Y' then
select PORT_NUMBER,STATIC_IP into outport,outIP from TINST where INST_ID = in_Hid and IP_PORT_STATUS = 'Y';
if sql%rowcount >= 1 then
select SOCK_ID into outSockid from TINST where PORT_NUMBER = outport AND STATIC_IP = outIP;
outretvalue := 0;
else
outretvalue := -12;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -13;
end if;
END;
Yes, you can use SQL%ROWCOUNT. It's valid in PL/SQL.
However, in PL/SQL the result of your query needs to go somewhere e.g. into a PL/SQL table. PL/SQL will never send the result to the output (terminal, window etc.). So SELECT * FROM won't work.
Your code could look like this:
DECLARE
TYPE emp_t ...;
emp_tab emp_t;
BEGIN
SELECT *
BULK COLLECT INTO emp_tab
FROM emp
WHERE empname = 'Justin' AND dept='IT';
IF sql%rowcount > 0 THEN
.. do something ...
END IF;
END;
/
Update:
The updated questions suggests that you're looking for something else.
Option 1: Use exceptions
If there are 0 rows or more than 1 row, these cases are handled separately (as errors):
BEGIN
select PORT_NUMBER,STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y';
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
RETURN;
WHEN TOO_MANY_ROWS THEN
outretvalue := -13;
RETURN;
END;
Option 2: Use aggregations
Using aggregations, the query will always return exactly one row. If now source row matched the WHERE clause, then both result values will be NULL. If there WHERE clause matched more than one row, the maximum will be taken.
Note that this query might return a port number and an IP address that originally were not on the same row.
select MAX(PORT_NUMBER), MAX(STATIC_IP) into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y';
IF outport IS NULL OR outIP IS NULL THEN
outretvalue := -12;
RETURN;
END IF;
Option 3: Use ROWNUM
This query returns at most one row. If no row matched the WHERE clause, an exception is thrown and needs to be handled:
BEGIN
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y'
AND ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
RETURN;
END;
Based on your comment
If 2nd 'select' query returns more than one row i want to take the first one and process with it
... this ought to work, but perhaps not quite as you expect, as you haven't defined what the 'first one' means.
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2, outInststatus OUT VARCHAR2,
outSockid IN NUMBER, outport OUT VARCHAR2, outIP OUT VARCHAR2,
outretvalue OUT NUMBER)
AS
BEGIN
select INST_STATUS into outInststatus
from TINST_child
where INST_ID = in_Hid and INST_STATUS = 'Y';
-- no need to check if outInstatus is Y, that's all it can be here
-- restricting with `rownum` means you'll get at most one row, so you will
-- not get too_many_rows. But it will be an arbitrary row - you have no
-- criteria to determine which of the multiple rows you want. And you can
-- still get no_data_found which will go to the same exception and set -12
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid and IP_PORT_STATUS = 'Y'
and rownum < 2;
-- no need to check sql%rowcount; it can only be 1 here
-- not clear if this can return multiple rows too, and what should happen
-- if it can; could use rownum restriction but with the same caveats
select SOCK_ID into outSockid
from TINST
where PORT_NUMBER = outport AND STATIC_IP = outIP;
outretvalue := 0;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
END;
The exception handler applies to the whole block. If any of the select statements find no rows, the no_data_found exception will be handled by that block and will set outretvalue to -12.
If you want a different outretvalue for each select then you can wrap them in sub-blocks, each with their own exception handling section:
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2, outInststatus OUT VARCHAR2,
outSockid IN NUMBER, outport OUT VARCHAR2, outIP OUT VARCHAR2,
outretvalue OUT NUMBER)
AS
BEGIN
BEGIN
select INST_STATUS into outInststatus
from TINST_child
where INST_ID = in_Hid and INST_STATUS = 'Y';
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
END;
BEGIN
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid and IP_PORT_STATUS = 'Y'
and rownum < 2;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -13;
END;
BEGIN
select SOCK_ID into outSockid
from TINST
where PORT_NUMBER = outport AND STATIC_IP = outIP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -14;
END;
outretvalue := 0;
END;
You only need to do that if the caller needs to know which select failed, and if you never really expect any of them to fail then it's probably more common not to catch the exception at all and let the caller see the raw no_data_found and decide what to do. Depends what the exception condition means to you and your application though.

Resources