Write a PL/SQL block to insert numbers into the MESSAGES table. Insert the numbers 1 through 10, excluding 6 and 8 - oracle

I'm trying to figure out how to exclude these numbers(6 and 8) when the loop happens. Also, for this question, I CAN'T use FOR and WHILE loops. The question states to ONLY use a basic loop since the lessons after will teach me how to use it. Also, does anyone know if I'm allowed to insert multiple END LOOPs? It's also possible that this syntax may not be legal.
EDIT: I'm pretty sure I've tried doing IF v_results >10 THEN EXIT; but the same error message occurred.
DECLARE
v_results messages.results%TYPE := 0 ; --data type is NUMBER
BEGIN
LOOP
SELECT results INTO v_results
FROM messages;
v_results := v_results + 1; --to increment
IF v_results = ANY(6,8)
THEN
END LOOP; --i thought maybe if I added this, the loop can start over
ELSE
INSERT INTO MESSAGES(results)
VALUES (v_results);
EXIT WHEN v_results >10;
END IF;
END LOOP;
END;
The error that I am getting.
ORA-06550: line 15, column 9:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with
<<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge

You could just avoid the 6 and 8 by adding 1 into it using the IF statement. There is no need for ELSE statement.
Also, you will need to use MAX function while fetching data from the MESSAGE table so that it can return the only max number. Without MAX function, you will get an error of -- multiple rows returned.
END LOOP must be associated with a single LOOP statement. so you can not write two END LOOP when there is a single LOOP statement.
DECLARE
V_RESULTS MESSAGES.RESULTS%TYPE := 0; --data type is NUMBER
BEGIN
LOOP
SELECT
MAX(RESULTS) -- used max to find ONLY ONE MAX RECORD
INTO V_RESULTS
FROM
MESSAGES;
V_RESULTS := NVL(V_RESULTS, 0) + 1; --to increment
IF V_RESULTS IN ( -- used in here
6,
8
) THEN
V_RESULTS := V_RESULTS + 1;
END IF;
INSERT INTO MESSAGES ( RESULTS ) VALUES ( V_RESULTS );
EXIT WHEN V_RESULTS >= 10;
END LOOP;
END;
db<>fiddle demo
Cheers!!

declare
i number:=1;
begin
loop
if i not in(6,8) then
insert into msg values(i);
end if;
i :=i+1;
exit when i>10;
end loop;
end;

Related

Ignore lines that causes errors

I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.

What is purpose of NULL at the end of this loop?

Revising an old program. It issues NULL; and the end of this loop.
Why? What is it supposed to do? Nothing as far as I can tell.
Here is the loop:
--Price Break Loop
OPEN c_price_breaks;
<<c_price_breaks_loop>>
l_prc_brk_count := 1;
LOOP
dbms_output.put_line('Price break loop..');
fnd_file.put_line(fnd_file.log,'Price break loop..');
--FOR prc_brk_cnt IN 1..L_prc_brk_count LOOP
FETCH c_price_breaks INTO l_price_override, l_ship_to_organization_id_pb, l_ship_to_organization_code_pb, l_ship_to_location_id_pb, l_ship_to_location_code_pb, l_quantity;
EXIT WHEN c_price_breaks%NOTFOUND;
--Inserting into line location interface table
INSERT INTO PO.PO_LINE_LOCATIONS_INTERFACE (
INTERFACE_LINE_LOCATION_ID,
INTERFACE_HEADER_ID,
INTERFACE_LINE_ID,
SHIPMENT_TYPE,
SHIPMENT_NUM,
SHIP_TO_ORGANIZATION_ID,
SHIP_TO_LOCATION_ID,
QUANTITY,
UNIT_OF_MEASURE,
PRICE_OVERRIDE,
CREATION_DATE
) VALUES (
po_line_locations_interface_s.nextval, --INTERFACE_LINE_LOCATION_ID,
po_headers_interface_s.currval, --INTERFACE_HEADER_ID,
po_lines_interface_s.currval, --INTERFACE_LINE_ID,
'QUOTATION', --SHIPMENT_TYPE,
l_prc_brk_count, --SHIPMENT_NUM,
l_ship_to_organization_id_pb, --SHIP_TO_ORGANIZATION_ID,
l_ship_to_location_id_pb, --SHIP_TO_LOCATION_ID,
l_quantity, --QUANTITY,
l_unit_of_measure, --UOM
l_price_override, --PRICE_OVERRIDE
SYSDATE --CREATION_DATE,
);
l_prc_brk_count := l_prc_brk_count+1;
<<END_of_c_price_breaks_loop>>
NULL;
END LOOP; --END of Price break loop
Yes, the NULL is required while you have a label at the end of the PL/SQL block formed by the LOOP.
The Oracle documentation for the GOTO statement states:
GOTO Statement
The GOTO statement branches unconditionally to a statement label or block label. The label must be unique within its scope and must precede an executable statement or a PL/SQL block. The GOTO statement transfers control to the labelled statement or block.
The <<END_of_c_price_breaks_loop>> is a label and it must precede an executable statement or a PL/SQL block; NULL; is such a statement.
DECLARE
l_prc_brk_count PLS_INTEGER;
BEGIN
<<c_price_breaks_loop>>
l_prc_brk_count := 1;
LOOP
dbms_output.put_line('Price break loop..');
EXIT WHEN l_prc_brk_count > 3;
l_prc_brk_count := l_prc_brk_count+1;
<<END_of_c_price_breaks_loop>>
NULL;
END LOOP; --END of Price break loop
END;
/
Outputs:
Price break loop..
Price break loop..
Price break loop..
Price break loop..
Removing the NULL;
DECLARE
l_prc_brk_count PLS_INTEGER;
BEGIN
<<c_price_breaks_loop>>
l_prc_brk_count := 1;
LOOP
dbms_output.put_line('Price break loop..');
EXIT WHEN l_prc_brk_count > 3;
l_prc_brk_count := l_prc_brk_count+1;
<<END_of_c_price_breaks_loop>>
END LOOP; --END of Price break loop
END;
/
Gives:
ORA-06550: line 13, column 3:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
( begin case declare exit for goto if loop mod null raise
return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
Removing the label:
DECLARE
l_prc_brk_count PLS_INTEGER;
BEGIN
<<c_price_breaks_loop>>
l_prc_brk_count := 1;
LOOP
dbms_output.put_line('Price break loop..');
EXIT WHEN l_prc_brk_count > 3;
l_prc_brk_count := l_prc_brk_count+1;
END LOOP; --END of Price break loop
END;
/
Gives:
Price break loop..
Price break loop..
Price break loop..
Price break loop..
db<>fiddle here
The null is there because there has to be a statement after a label. If you try to run this block, for example
declare
l_cnt integer := 0;
begin
loop
exit when l_cnt > 1;
l_cnt := l_cnt + 1;
<<foo>>
end loop;
end;
you'll get a syntax error because the label foo doesn't have an executable statement after it. If you add a null;, the block compiles
declare
l_cnt integer := 0;
begin
loop
exit when l_cnt > 1;
l_cnt := l_cnt + 1;
<<foo>>
null;
end loop;
end;
Now, it doesn't appear that either of the labels in the code you posted are actually doing anything useful themselves (other than maybe functioning as comments?). Unless you have code somewhere else that has a goto one of these labels, you could eliminate both the c_price_breaks_loop label and the END_of_c_price_breaks_loop label and then you can get rid of the null; statement.

ORA-01007 "variable not in select list" from dbms_sql.column_value call

I am trying to use dynamic SQL to sample all the data in a schema with a pattern:
DECLARE
xsql varchar2(5000);
c NUMBER;
d NUMBER;
col_cnt INTEGER;
f BOOLEAN;
rec_tab DBMS_SQL.DESC_TAB;
col_num NUMBER;
varvar varchar2(500);
PROCEDURE print_rec(rec in DBMS_SQL.DESC_REC) IS
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('col_type = '
|| rec.col_type);
DBMS_OUTPUT.PUT_LINE('col_maxlen = '
|| rec.col_max_len);
DBMS_OUTPUT.PUT_LINE('col_name = '
|| rec.col_name);
DBMS_OUTPUT.PUT_LINE('col_name_len = '
|| rec.col_name_len);
DBMS_OUTPUT.PUT_LINE('col_schema_name = '
|| rec.col_schema_name);
DBMS_OUTPUT.PUT_LINE('col_schema_name_len = '
|| rec.col_schema_name_len);
DBMS_OUTPUT.PUT_LINE('col_precision = '
|| rec.col_precision);
DBMS_OUTPUT.PUT_LINE('col_scale = '
|| rec.col_scale);
DBMS_OUTPUT.PUT('col_null_ok = ');
IF (rec.col_null_ok) THEN
DBMS_OUTPUT.PUT_LINE('true');
ELSE
DBMS_OUTPUT.PUT_LINE('false');
END IF;
END;
BEGIN
c := DBMS_SQL.OPEN_CURSOR;
xsql:='
WITH got_r_num AS
(
SELECT e.* -- or whatever columns you want
, ROW_NUMBER () OVER (ORDER BY dbms_random.value) AS r_num
FROM dba_tab_columns e
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num <= 10';
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
NULL;
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
--dbms_output.put_line('varvar=');
--DBMS_SQL.COLUMN_VALUE(source_cursor, 2, name_var);
--DBMS_SQL.COLUMN_VALUE(source_cursor, 3, birthdate_var);
-- Bind the row into the cursor that inserts into the destination table. You
-- could alter this example to require the use of dynamic SQL by inserting an
-- if condition before the bind.
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':id_bind', id_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':name_bind', name_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':birthdate_bind',
--birthdate_var);
--ignore := DBMS_SQL.EXECUTE(destination_cursor);
--ELSE
-- No more rows to copy:
--EXIT;
END IF;
END LOOP;
--EXIT WHEN d != 10;
--END LOOP;
col_num := rec_tab.first;
IF (col_num IS NOT NULL) THEN
LOOP
print_rec(rec_tab(col_num));
col_num := rec_tab.next(col_num);
EXIT WHEN (col_num IS NULL);
END LOOP;
END IF;
DBMS_SQL.CLOSE_CURSOR(c);
END;
/
When I run that it gives me this error from the line with the dbms_sql.column_value call:
ORA-01007: variable not in select list
If I comment out that dbms_sql.column_value call it still errors but now with:
ORA-01002: fetch out of sequence
What am I doing wrong?
You have two problems in the code you posted. Firstly you have skipped part of the execution flow because you haven't called the DEFINE_COLUMN procedure. That is what is causing the ORA-01007 error, as the dynamic SQL processing hasn't been told about the select list columns via that call. For your current code you only need to define column 2, but assuming you will actually want to refer to the others you can define them in a loop. To treat them all as string for display you could do:
...
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
FOR i IN 1..col_cnt
LOOP
-- dbms_output.put_line('col_name is ' || rec_tab(i).col_name);
DBMS_SQL.DEFINE_COLUMN(c, i, varvar, 500);
END LOOP;
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
...
If you want to do anything that needs to treat the variables as the right types you could have a local variable of each type and use the data type from the rec_tab information you already have from describe_columns to use the appropriately typed variable for each column.
The second problem, which you were hitting when you commented the column_value call, is still there once that definbe issue has been fixed. Your loop doesn't ever exit, so after you fetch the last row from the cursor you do a further invalid fetch, which throws ORA-01002. You have the code to avoid that already but it's commented out:
...
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
...
ELSE
-- No more rows to copy:
EXIT;
END IF;
END LOOP;
...
With those two changes your code runs, and dumps the view structure:
PL/SQL procedure successfully completed.
col_type = 1
col_maxlen = 30
col_name = OWNER
col_name_len = 5
col_schema_name =
col_schema_name_len = 0
col_precision = 0
col_scale = 0
col_null_ok = false
col_type = 1
col_maxlen = 30
col_name = TABLE_NAME
...
To those who find this question when accessing Oracle through ODP.NET, as I did:
We started getting this error whenever we would add column to an existing table in our application. I'm not sure what all the conditions were to make it fail, but ours were:
Run a SELECT * FROM "table".
Include a ROWNUM restriction in the WHERE clause (WHERE ROWNUM < 10).
Run that through the ODP.NET dataReader.GetSchemaTable() call.
Running unrestricted queries or running queries directly on Oracle SQL Developer did not seem to cause the error.
I've hit some pretty weird stuff in the past with Oracle connection pooling, so I eventually thought that could be the problem. The solution was to restart the web service to force all the connections to be fully dropped and recreated.
The theory is that the ODP.NET connection from the connection pool still had no idea the column existed on the table, but the column was returned by the database.

PLS-00103: Encountered the symbol "1" when expecting one of the following: (

Wrote the following PLSQL script to produce a report and am getting the error messages
Error at line 5
ORA-06550: line 61, column 18:
PLS-00103: Encountered the symbol "1" when expecting one of the following:
(
I've been through the code many times and I cannot find the error. Any help would be greatly appreciated.
I'm currently working in Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
SET serveroutput ON size 1000000;
DECLARE
TYPE TITLE_RECORD_TYPE IS RECORD
(id number(19),
gaid varchar2(20),
artist_legal_name varchar2(510),
artist_display_title varchar2(510),
display_title varchar2(510),
category varchar2(255),
type varchar2(255),
sub_type varchar2(255));
TITLE_RECORD TITLE_RECORD_TYPE;
v_title varchar2(510);
v_artist varchar2(510);
v_total_rows_error number(20) := 0;
v_row_count number(10) := 0;
v_error_desc varchar2(200) := NULL;
v_error_code number(19);
CURSOR ARTIST_TITLE_CURSOR is
select track_artist,track_title
from asset_artist_title;
CURSOR QUERY_CURSOR is
select distinct g1.gaid,g2.legal_name,g1.artist_display_title,
g1.display_title,g1.category,g1.type,g1.sub_type
from gcdm_app_rpt.rpt_asset g1,
gcdm_app_rpt.rpt_artist g2
where g1.artist_id = g2.id
and g1.is_deleted <> 'Y'
and g1.is_core = 'Y'
and g2.is_core = 'Y'
and g1.title like v_title||'%'
and g1.artist_display_title like v_artist||'%';
BEGIN
OPEN ARTIST_TITLE_CURSOR;
LOOP
FETCH ARTIST_TITLE_CURSOR into v_artist,v_title;
EXIT WHEN ARTIST_TITLE_CURSOR%NOTFOUND or ARTIST_TITLE_CURSOR%NOTFOUND IS NULL;
SELECT count(*)
INTO v_row_count
FROM gcdm_app_rpt.rpt_asset g1,
gcdm_app_rpt.rpt_artist g2
WHERE g1.artist_id = g2.id
AND g1.is_core = 'Y'
AND g1.is_deleted <> 'Y'
AND g2.is_core = 'Y'
AND g1.title like v_title||'%'
AND g1.artist_display_title like v_artist||'%';
IF v_row_count < 1 THEN
v_error_desc := 'Matching Asset record for '||v_artist||' - '||v_title||' not found';
DBMS_OUTPUT.PUT_LINE('Error: '||v_error_desc||'.');
v_row_count := 0;
v_total_rows_error := v_total_rows_error + 1;
ELSE
OPEN QUERY_CURSOR
FOR i in 1..ARTIST_TITLE_CURSOR
LOOP
FETCH QUERY_CURSOR into TITLE_RECORD;
EXIT WHEN QUERY_CURSOR%NOTFOUND or QUERY_CURSOR%NOTFOUND IS NULL;
DBMS_OUTPUT.PUT_LINE(title_record.id,title_record.gaid,title_record.artist_legal_name,title_record.artist_display_name,
title_record.display_title,title_record.category,title_record.type,title_record.sub_type);
END LOOP;
CLOSE QUERY_CURSOR;
v_row_count := 0;
END IF;
END LOOP;
CLOSE ARTIST_TITLE_CURSOR;
DBMS_OUTPUT.PUT_LINE(chr(0));
IF v_total_rows_error > 0 THEN
DBMS_OUTPUT.PUT_LINE('Total Rows in error: '||v_total_rows_error);
END IF;
DBMS_OUTPUT.PUT_LINE(CHR(0));
EXCEPTION
WHEN OTHERS THEN
v_error_desc := SQLERRM;
v_error_code := SQLCODE;
DBMS_OUTPUT.PUT_LINE('Error: '||v_error_desc||' - '||v_error_code);
END;
It's line 67 in what you've posted, not 61, but still; this line is not right:
FOR i in 1..ARTIST_TITLE_CURSOR
You're trying to loop over a range of numbers - perhaps you wanted the number of records returned by the cursor, which you can't get - but your end 'number' is a cursor, so not legal in that context.
But it seems to be completely out of place anyway as you're looping over the QUERY_CURSOR records, so I wouldn't think the ARTIST_TITLE_CURSOR is relevant at this point. And you aren't attempting to use i. It looks like you can just remove that line.
More importantly, the previous line is missing a semi-colon:
OPEN QUERY_CURSOR;
Because it doesn't have one it's seeing the FOR and expecting a cursor query.
Following up on comments about why you have that FOR 1..v_row_count, it's still a bit redundant. You're limiting the number of fetches you do to match the count you got previously, from essentially the same query as you have in the cursor, which means you don't quite ever hit the EXIT WHEN QUERYCURSOR%NOTFOUND condition - that would come from the v_row_count+1 loop iteration. Normally you wouldn't know how many rows you expect to see before you loop over a cursor.
You don't really need to know here. The count query is repetitive - you're querying the same data you then have to hit again for the cursor, and you have to maintain the query logic in two places. It would be simpler to forget the count step, and instead keep a counter as you loop over the cursor; then handle the zero-rows condition after the loop. For example:
DECLARE
...
BEGIN
OPEN ARTIST_TITLE_CURSOR;
LOOP
FETCH ARTIST_TITLE_CURSOR into v_artist,v_title;
EXIT WHEN ARTIST_TITLE_CURSOR%NOTFOUND;
-- initialise counter for each ARTIST_TITLE
v_row_count := 0;
OPEN QUERY_CURSOR;
LOOP
FETCH QUERY_CURSOR into TITLE_RECORD;
EXIT WHEN QUERY_CURSOR%NOTFOUND;
-- increment 'found' counter
v_row_count := v_row_count + 1;
DBMS_OUTPUT.PUT_LINE(title_record.id
||','|| title_record.gaid
||','|| title_record.artist_legal_name
||','|| title_record
||','|| artist_display_name
||','|| title_record.display_title
||','|| title_record.category
||','|| title_record.type
||','|| title_record.sub_type);
END LOOP;
CLOSE QUERY_CURSOR;
-- now check if we found anything in the QUERY_CURSOR loop
IF v_row_count < 1 THEN
v_error_desc := 'Matching Asset record for '||v_artist||' - '||v_title||' not found';
DBMS_OUTPUT.PUT_LINE('Error: Matching Asset record for '
|| v_artist || ' - ' || v_title || ' not found.');
v_total_rows_error := v_total_rows_error + 1;
END IF;
END LOOP;
CLOSE ARTIST_TITLE_CURSOR;
--DBMS_OUTPUT.PUT_LINE(chr(0));
-- presumably this was meant to put out a blank line; use this instead
DBMS_OUTPUT.NEW_LINE;
IF v_total_rows_error > 0 THEN
DBMS_OUTPUT.PUT_LINE('Total Rows in error: '||v_total_rows_error);
END IF;
--DBMS_OUTPUT.PUT_LINE(CHR(0));
DBMS_OUTPUT.NEW_LINE;
END;
I've also taken out the exception handler because it isn't really adding anything; you'd see the code and message without it, even if you didn't have server output on; and catching WHEN OTHERS is a bad habit to get into.
You also don't need to declare your record type. You could use an implicit cursor anyway and avoid the type and variable completely, but even with the cursor definition you have, you could put this afterwards instead:
TITLE_RECORD QUERY_CURSOR%ROWTYPE;
There are various ways to open and loop over cursors, and you're using one of the more explicit ones - which isn't a bad thing for learning about them, but be aware of the options too.

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