Is it possible to CONTINUE a loop from an exception? - oracle

I have a fetch being executed inside of a loop. If this fetch fails (no data) I would like to CONTINUE the loop to the next record from within the EXCEPTION.
Is this possible?
I'm getting a ORA-06550 & PLS-00201 identifer CONTINUE must be declared
DECLARE
v_attr char(88);
CURSOR SELECT_USERS IS
SELECT id FROM USER_TABLE
WHERE USERTYPE = 'X';
BEGIN
FOR user_rec IN SELECT_USERS LOOP
BEGIN
SELECT attr INTO v_attr
FROM ATTRIBUTE_TABLE
WHERE user_id = user_rec.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- user does not have attribute, continue loop to next record.
CONTINUE;
END;
END LOOP;
END;

The CONTINUE statement is a new feature in 11g.
Here is a related question: 'CONTINUE' keyword in Oracle 10g PL/SQL

In the construct you have provided, you don't need a CONTINUE. Once the exception is handled, the statement after the END is performed, assuming your EXCEPTION block doesn't terminate the procedure. In other words, it will continue on to the next iteration of the user_rec loop.
You also need to SELECT INTO a variable inside your BEGIN block:
SELECT attr INTO v_attr FROM attribute_table...
Obviously you must declare v_attr as well...

How about the ole goto statement (i know, i know, but it works just fine here ;)
DECLARE
v_attr char(88);
CURSOR SELECT_USERS IS
SELECT id FROM USER_TABLE
WHERE USERTYPE = 'X';
BEGIN
FOR user_rec IN SELECT_USERS LOOP
BEGIN
SELECT attr INTO v_attr
FROM ATTRIBUTE_TABLE
WHERE user_id = user_rec.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- user does not have attribute, continue loop to next record.
goto end_loop;
END;
<<end_loop>>
null;
END LOOP;
END;
Just put end_loop at very end of loop of course. The null can be substituted with a commit maybe or a counter increment maybe, up to you.

For this example you really should just use an outer join.
declare
begin
FOR attr_rec IN (
select attr
from USER_TABLE u
left outer join attribute_table a
on ( u.USERTYPE = 'X' and a.user_id = u.id )
) LOOP
<process records>
<if primary key of attribute_table is null
then the attribute does not exist for this user.>
END LOOP;
END;

Notice you can use WHEN exception THEN NULL the same way as you would use WHEN exception THEN continue. Example:
DECLARE
extension_already_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(extension_already_exists, -20007);
l_hidden_col_name varchar2(32);
BEGIN
FOR t IN ( SELECT table_name, cast(extension as varchar2(200)) ext
FROM all_stat_extensions
WHERE owner='{{ prev_schema }}'
and droppable='YES'
ORDER BY 1
)
LOOP
BEGIN
l_hidden_col_name := dbms_stats.create_extended_stats('{{ schema }}', t.table_name, t.ext);
EXCEPTION
WHEN extension_already_exists THEN NULL; -- ignore exception and go to next loop iteration
END;
END LOOP;
END;

Related

Calling Cursor Value into Exception

I have a cursor as part of a package, which compares the counts of 2 tables. If the counts match, rest of the package executes. But if it fails, it should update the counts in a log table.
The cursor code is
CURSOR C_CNT IS
SELECT
lnd.sml_batchrun_id batch_id,
lnd.lnd_count,
dwh.dwh_count
FROM
(
SELECT
sml_batchrun_id,
COUNT(*) lnd_count
FROM
iva_landing.lnd_sml_t
GROUP BY
sml_batchrun_id
) lnd
LEFT JOIN (
SELECT
batchrun_id,
sent_records_count dwh_count,
dwh_sending_table
FROM
dwh.dwh_idh_to_iva_metadata_t
) dwh ON dwh.batchrun_id = lnd.sml_batchrun_id
WHERE dwh.dwh_sending_table = 'DWH_SML_T'
ORDER BY
1 DESC;
The comparison code is:
FOR L_COUNT IN C_CNT LOOP --0001,0002
IF L_COUNT.lnd_count = L_COUNT.dwh_count THEN
UPDATE DWH.DWH_IDH_TO_IVA_METADATA_T idh
SET idh.IVA_RECEIVING_TABLE = 'LND_SML_T',
idh.RECEIVED_DATE = SYSDATE,
idh.RECEIVED_RECORDS_COUNT = L_COUNT.lnd_count,
idh.status = 'Verified'
WHERE L_COUNT.batch_id = idh.batchrun_id
AND idh.dwh_sending_table = 'DWH_SML_T';
COMMIT;
ELSE
RAISE EXCPT_SML_MISSDATA; -- Throw error and exit process
END IF;
END LOOP;
Now, in the Exception Handling part, I want to display the counts in the error column of the log table
logger.log_error('IVA-MSG 200-010 - COUNT MISMATCH! - Aborting', p_log_id=>l_job_log.log_id);
l_job_log.end_date := systimestamp;
l_job_log.error_mesg := cur_cnt.dwh_count||' '|| cur_cnt.lnd_count;
l_job_log.status := iva_log.iva_job_log_pck.c_str_statusfailed;
iva_log.iva_job_log_pck.change_joblog_prc(pi_rec_joblog => l_job_log);
RAISE;
Here, cur_cnt is a variable defined as cur_cnt c_cnt%rowtype;
and l_job_log as l_job_log iva_log.iva_job_log_t%rowtype;
where iva_job_log_t is the log table name.
But after triggering the package the count is not visible in the error column. Also, if I put something in single quotes for the iva_job_log_t then it gets displayed in the log table.
Please suggest how to display the counts from the cursor.
Thanks
If you are still doing
FOR L_COUNT IN C_CNT LOOP
then your cur_cnt variable is never going to be populated; it will be the empty record it was declared as, so referring to any of its fields will always give you null.
You can just refer to the l_count values in the error log:
l_job_log.error_mesg := l_count.dwh_count ||' '|| l_count.lnd_count;
... as you do elsewhere inside the loop.
If the exception handler is currently later in the process, so l_count is out of scope (as the re-raise possibly suggests) then you could move it so it's within the else instead of being separated:
FOR L_COUNT IN C_CNT LOOP --0001,0002
IF L_COUNT.lnd_count = L_COUNT.dwh_count THEN
UPDATE DWH.DWH_IDH_TO_IVA_METADATA_T idh
SET idh.IVA_RECEIVING_TABLE = 'LND_SML_T',
idh.RECEIVED_DATE = SYSDATE,
idh.RECEIVED_RECORDS_COUNT = L_COUNT.lnd_count,
idh.status = 'Verified'
WHERE L_COUNT.batch_id = idh.batchrun_id
AND idh.dwh_sending_table = 'DWH_SML_T';
COMMIT;
ELSE
logger.log_error('IVA-MSG 200-010 - COUNT MISMATCH! - Aborting', p_log_id=>l_job_log.log_id);
l_job_log.end_date := systimestamp;
l_job_log.error_mesg := l_count.dwh_count||' '|| l_count.lnd_count;
l_job_log.status := iva_log.iva_job_log_pck.c_str_statusfailed;
iva_log.iva_job_log_pck.change_joblog_prc(pi_rec_joblog => l_job_log);
RAISE EXCPT_SML_MISSDATA; -- Throw error and exit process
END IF;
END LOOP;
Otherwise you would have to change the cursor loop handling to fetch into the variable, refer to that within the loop instead, and rely on that having the right values later:
OPEN C_CNT
LOOP
FETCH C_CNT INTO CUR_CNT;
EXIT WHEN C_CNT%NOTFOUND;
IF CUR_CNT.lnd_count = CUR_CNT.dwh_count THEN
UPDATE DWH.DWH_IDH_TO_IVA_METADATA_T idh
SET idh.IVA_RECEIVING_TABLE = 'LND_SML_T',
idh.RECEIVED_DATE = SYSDATE,
idh.RECEIVED_RECORDS_COUNT = CUR_CNT.lnd_count,
idh.status = 'Verified'
WHERE CUR_CNT.batch_id = idh.batchrun_id
AND idh.dwh_sending_table = 'DWH_SML_T';
COMMIT;
ELSE
RAISE EXCPT_SML_MISSDATA; -- Throw error and exit process
END IF;
END LOOP;
Your later exception handler should then work as it is.

inserting data select into with loop in plsql

I am just want to enter forms detail column data into a database column from a detail block and that must check first entered data which it already have in that column when i entered data data saves in other tables but in token_staus is not entering data and message me No data found for that i write a loop which is not working for me i am making some kind of mistake obviously but not sure where it is
DECLARE
TOKEN_NO NUMBER;
TOKEN_STATUS1 NUMBER;
--TOKEN_STATUS2 := :TOKEN_STATUS;
BEGIN
SELECT SR_NO, TOKEN_STATUS INTO TOKEN_NO, TOKEN_STATUS1 FROM LOOPT2 WHERE SR_NO = :TOKEN_NO;
--IF :TOKEN_STATUS IS NULL
--THEN
LOOP
GO_BLOCK('TOKEN_REC2');
FIRST_RECORD;
INSERT INTO LOOPT2(TOKEN_STATUS) VALUES(:TOKEN_STATUS);
NEXT_RECORD;
EXIT WHEN :SYSTEM.LAST_RECORD = 'TRUE';
END LOOP;
--END IF;
EXCEPTION
when others then
message (sqlerrm);
END;
Please help me
Little Foot please
If I understood you correctly, it would be something like this (check comments within the code): key thing here seems to be a BEGIN-EXCEPTION-END block within the LOOP.
DECLARE
TOKEN_NO NUMBER;
TOKEN_STATUS1 NUMBER;
BEGIN
GO_BLOCK('TOKEN_REC2');
FIRST_RECORD;
LOOP
BEGIN
SELECT SR_NO, TOKEN_STATUS
INTO TOKEN_NO, TOKEN_STATUS1
FROM LOOPT2
WHERE SR_NO = :TOKEN_NO;
-- if such a :TOKEN_NO exists, that SELECT will return some values
-- which means that you want to skip it, so - don't do anything
EXCEPTION
-- if such a :TOKEN_NO does not exist, SELECT will return
-- NO_DATA_FOUND which means that you want to perform insert
WHEN NO_DATA_FOUND THEN
INSERT INTO LOOPT2 (TOKEN_STATUS)
VALUES (:TOKEN_STATUS);
WHEN TOO_MANY_ROWS THEN
NULL;
END;
EXIT WHEN :SYSTEM.LAST_RECORD = 'TRUE';
NEXT_RECORD;
END LOOP;
END;

Creating a result set (using a select statement ) within a loop

I have created a cursor which returns me a set of rows. While iterating through each of the row, I want to get another result set (by forming a SELECT statement by with a WHERE clause having value from the processed row) from another table. I am a newbie in PLSQL. Can you please guide me on how this could be done? (Can we have a Cursor defined inside the loop while looping for the resultset of the cursor)?
Please excuse me if I am not able to make myself clear.
Thanks in advance
DECLARE
CURSOR receipts IS
SELECT CREATED_T, ACCT_NO, AMT FROM receipt_t
WHERE OBJ_TYPE='misc';
receipts_rec receipts%ROWTYPE;
BEGIN
-- Open the cursor for processing
IF NOT receipts%ISOPEN THEN
OPEN receipts;
END IF;
LOOP
FETCH receipts INTO receipts_rec;
EXIT WHEN receipts%NOTFOUND;
/* Loop through each of row and get the result set from another table */
newQuery := 'SELECT * FROM ageing_data WHERE ACCT_NO = ' || receipts_rec.ACCT_NO;
-- Execute the above query and get the result set, say RS
LOOP
-- For above result set-RS
END LOOP;
END LOOP;
CLOSE receipts;
END;
Yes, you can define a cursor that takes a set of parameters and use those values in the WHERE clause.
DECLARE
CURSOR c_cursor1 IS
SELECT field1, field2, ... , fieldN
FROM table1
WHERE conditions;
CURSOR c_cursor2 (p_parameter NUMBER) IS
SELECT field1, field2, ..., fieldN
FROM table2
WHERE table2.field1 = p_parameter;
BEGIN
FOR record1 IN c_cursor1 LOOP
FOR record2 IN c_cursor2(record1.field1) LOOP
dbms_output.put_line('cursor 2: ' || record2.field1);
END LOOP
END LOOP;
END;
Yes, you can do that, but there is absolutely no reason to. Try the following:
BEGIN
FOR aRow IN (SELECT rt.CREATED_T, rt.ACCT_NO, rt.AMT, ad.*
FROM RECEIPT_T rt
INNER JOIN AGEING_DATA ad
ON (ad.ACCT_NO = rt.ACCT_NO)
WHERE rt.OBJ_TYPE='misc')
LOOP
-- Process the data in aRow here
END LOOP;
END;
This does exactly the same work as the original "loop-in-a-loop" structure but uses the database to join the tables together on the common criteria instead of opening and closing cursors multiple times.
Share and enjoy.
Something like this can be done in the following manner:
DECLARE
CURSOR cursor1 IS
SELECT *
FROM table1;
CURSOR cursor2 IS
SELECT *
FROM table2
WHERE column1 = I_input_param;
BEGIN
FOR table_1_rec in cursor1 LOOP
I_input_param := table_1_rec.column_1;
FOR table_2_rec in cursor2 LOOP
....
....
END LOOP;
END LOOP;
END;
I have used an implicit open/fetch here. I hope you get the idea.

how inner cursor use a field of outer cursor in oracle?

I have two nested cursors. I mean one cursor Is Inside of another one.
I want to use a field of outer cursor inside the inner one. something like this:
Inner_cursor.outer_cursor.outer_cursor_column;
but It does not work even I use like this:
Inner_cursor.(outer_cursor.outer_cursor_column);
Is there any way I could do this?
EDIT:
This Is My Code:
CREATE OR REPLACE PROCEDURE TEST1
AS
CURSOR loop_relation IS
SELECT * FROM RELATION_table;
relation_rec loop_relation%rowtype;
CURSOR loop_BIG_TABLE IS
SELECT * FROM BIG_TABLE;
BIG_TABLE_rec loop_BIG_TABLE%rowtype;
BEGIN
FOR RELATION_REC IN LOOP_RELATION
LOOP
FOR BIG_TABLE_rec in loop_BIG_TABLE
LOOP
IF (BIG_TABLE_REC.RELATION_REC.DESTINATION_PK IS NULL) THEN
UPDATE BIG_TABLE
SET BIG_TABLE.RELATION_REC.DESTINATION_PK = (
SELECT RELATION_REC.SOURCE_FK FROM RELATION_REC.SOURCE_TABLE
WHERE RELATION_REC.SOURCE_PK = BIG_TABLE_REC.RELATION_REC.SOURCE_PK)
WHERE BIG_TABLE_REC.ID = BIG_TABLE.ID;
END IF;
END LOOP;
END LOOP;
END TEST1;
/
my problem is in the lines that i use three dot(.) to use a value of outer cursor in inner cursor.
Here's an example of two nested cursors and variables from the outer one used in the inner one. I hope it helps you.
BEGIN
FOR r_outer in (
select tab1.field1
from table1 tab1 )
LOOP
FOR r_inner in (
select tab2.field2
from table2 tab2
where tab2.field2 = r_outer.field1 )
LOOP
dbms_output.put_line(r_outer.field1);
dbms_output.put_line(r_inner.field2);
END LOOP;
END LOOP;
END;
For reference, I created a procedure to display how to use outer cursor value inside inner cursor value. I hope this resolves your query.
CREATE OR REPLACE PROCEDURE cur_inside_cur(my_cur OUT sys_refcursor)
AS
CURSOR roy_cur IS
SELECT name FROM avrajit;
roy_cur1 roy_cur%ROWTYPE;
BEGIN
OPEN roy_cur;
LOOP
FETCH roy_cur INTO roy_cur1;
EXIT WHEN roy_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(roy_cur1.name);
OPEN my_cur FOR
SELECT department FROM avrajit
WHERE name=roy_cur1.name;
END LOOP;
END cur_inside_cur;
OUTPUT
var c refcursor;
begin
cur_inside_cur(:c);
end;
print c;

Are there alternative methods for saying 'next' in a pl/sql for loop?

So I've got a for loop that processes a list of IDs and has some fairly complex things to do. Without going into all the ugly details, basically this:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
-- NEXT;! NOOP;! but there is no next!
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
IF v_dup_check IS NULL THEN
-- if not a duplicate record, proceed...
ELSE
-- reset duplicate check variable
v_dup_check := NULL;
END;
END LOOP;
END;
How I normally handle this is by selecting into a value, and then wrap the following code in an IF statement checking to make sure that duplicate check variable is NULL. But it's annoying. I just want to be able to say NEXT; or NOOP; or something. Especially since I already have to catch the NO_DATA_FOUND exception. I suppose I could write a letter to Oracle, but I'm curious how others handle this.
I could also wrap this in a function, too, but I was looking for something a little cleaner/simpler.
Oracle 11g adds a C-style "continue" loop construct to PL/SQL, which syntactically sounds like what you're looking for.
For your purposes, why not just eliminate the duplicates prior to entering the loop? This could be done by querying l_selected using a table function, and then filtering out records you don't want instead of iterating over every value. Something like...
declare
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
cursor no_dups_cur (p_selected APEX_APPLICATION_GLOBAL.VC_ARR2) is
select * from (
select selected.*,
count(*) over (partition by county_id) cnt -- analytic to find counts grouped by county_id
from table(p_selected) selected -- use table function to treat VC_ARR2 like a table
) where cnt = 1 -- remove records that have duplicate county_ids
;
begin
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
for i in no_dups_cur(l_selected) loop
null; -- do whatever to non-duplicates
end loop;
end;
Just substitute the logic for determining a "duplicate" with your own (didn't have enough info from your example to really answer that part)
Instead of catching NO_DATA_FOUND, how about SELECTing the number of matching entries into a variable, say l_count, and proceeding if this count works out to be zero? Something like the following:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
l_count INTEGER;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will count duplicate entries, so we can noop if duplicate is found
SELECT COUNT(*) INTO l_count FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
IF l_count = 0 THEN
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
END IF;
END LOOP;
END;
To count the number of rows is also possible (see Pourquoi Litytestdata) but you can also do what you want to do in the when_no_data_found exception block.
declare
l_selected apex_application_global.vc_arr2;
l_county_id org_county_accountable.count_id%type;
begin
l_selected := apex_util.string_to_table(:p4_select_lst);
for i in l_selected.first..l_selected.last loop
begin
select count_id
into l_county_id
from org_county_accountable
where organization_id = :p4_id
and county_id = v_county_id;
exception
when no_data_found then
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
end;
end loop;
end;
<xmp>
<<next_loop>>
loop
...
...
if ....
then
goto next_loop;
</xmp>
This is a case where a GOTO statement might be useful. See the Oracle Documentation in the control structures to see how to do this. Also, you may want to search around here to find out how to query for the existence of a record. Running a query and waiting for an exception isn't optimal.
Another way - turn the check into a local function:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
v_dup_check org_county_accountable.county_id%TYPE;
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN TRUE;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we have code we only want to execute if there are no dupes already
IF NOT dup_exists (:P4_ID, v_county_id) THEN
-- if not a duplicate record, proceed...
END;
END LOOP;
END;
Of course, the local function could be re-written to use the count method if you prefer:
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
l_count INTEGER;
BEGIN
SELECT COUNT(*) INTO l_count
FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN (l_count > 0);
END;
Another method is to raise and handle a user-defined exception:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
duplicate_org_county EXCEPTION;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
BEGIN
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
RAISE duplicate_org_county;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
EXCEPTION
WHEN duplicate_org_county THEN NULL;
END;
END LOOP;
END;
I wouldn't normally do this, but if there were half a dozen reasons to jump to the next record, this might be preferable to multiple nested IFs.
I know this is an oldie but I couldn't help notice that none of the answers above take into account the cursor attributes:
There are four attributes associated with cursors: ISOPEN, FOUND, NOTFOUND, and ROWCOUNT. These attributes can be accessed with the % delimiter to obtain information about the state of the cursor.
The syntax for a cursor attribute is:
cursor_name%attribute
where cursor_name is the name of the explicit cursor.
So in this case you could use ROWCOUNT (which indicates the number of rows fetched so far) for your purposes, like this:
declare
aux number(10) := 0;
CURSOR cursor_name is select * from table where something;
begin
select count(*) into aux from table where something;
FOR row IN cursor_name LOOP
IF(aux > cursor_name%ROWCOUNT) THEN 'do something is not over';
ELSE 'do something else';
END IF;
END LOOP;
end;

Resources