'CONTINUE' keyword in Oracle 10g PL/SQL - oracle

I'm migrating a TSQL stored procedure to PL/SQL and have encountered a problem - the lack of a CONTINUE keyword in Oracle 10g.
I've read that Oracle 11g has this as a new feature, but upgrading is not an option unfortunately.
Is there any alternative to CONTINUE in 10g? I don't believe it's practical to restructure the logic of the SP as a work-around, because I have an outer loop, an IF, then a nested IF, then the CONTINUE at the end of a statement block within that IF.
Any help would be greatly appreciated, cheers.

You can simulate a continue using goto and labels.
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..50 LOOP
IF done THEN
GOTO end_loop;
END IF;
<<end_loop>> -- not allowed unless an executable statement follows
NULL; -- add NULL statement to avoid error
END LOOP; -- raises an error without the previous NULL
END;

Though it's a bit complex and just a fake, you can use exception this way :
DECLARE
i NUMBER :=0;
my_ex exception;
BEGIN
FOR i IN 1..10
LOOP
BEGIN
IF i = 5 THEN
raise my_ex;
END IF;
DBMS_OUTPUT.PUT_LINE (i);
EXCEPTION WHEN my_ex THEN
NULL;
END;
END LOOP;
END;

In fact, PL SQL does have something to replace CONTINUE. All you have to do is to add a label (a name) to the loop :
declare
i integer;
begin
i := 0;
<<My_Small_Loop>>loop
i := i + 1;
if i <= 3 then goto My_Small_Loop; end if; -- => means continue
exit;
end loop;
end;

For future searches, in oracle 11g they added a continue statement, which can be used like this :
SQL> BEGIN
2 FOR i IN 1 .. 5 LOOP
3 IF i IN (2,4) THEN
4 CONTINUE;
5 END IF;
6 DBMS_OUTPUT.PUT_LINE('Reached on line ' || TO_CHAR(i));
7 END LOOP;
8 END;
9 /
Reached on line 1
Reached on line 3
Reached on line 5
PL/SQL procedure successfully completed.

It's not available in 10g, however it's a new feature in 11G

Can you refactor the IFs into a function, returning at the appropriate point (early if necessary). Then the control flow will pick up in the loop at the right place.
Does that make sense?

Not exactly elegant, but simple:
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..50 LOOP
IF done THEN
NULL;
ELSE
<do loop stuff>;
END IF;
END LOOP;
END;

In Oracle there is a similar statement called EXIT that either exits a loop or a function/procedure (if there is no loop to exit from). You can add a WHEN to check for some condition.
You could rewrite the above example as follows:
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..50 LOOP
EXIT WHEN done;
END LOOP;
END;
This may not be enough if you want to exit from deep down some nested loops and logic, but is a lot clearer than a couple of GOTOs and NULLs.

This isn't exactly an answer to the question, but nevertheless worth noting:
The continue statement in PL/SQL and all other programming languages which use it the same way, can easily be misunderstood.
It would have been much wiser, clearer and more concise if the programming language developers had called the keyword skip instead.
For me, with a background of C, C++, Python, ... it has always been clear what `continue' means.
But without that historical background, you might end intepreting this code
for i in .. tab_xy.count loop
CONTINUE WHEN some_condition(tab_xy(i));
do_process(tab_xy(i));
end loop;
like this:
Loop through the records of the table tab_xy.
Continue if the record fulfills some_condition, otherwise ignore this record.
Do_process the record.
This interpretation is completely wrong, but if you imagine the PL/SQL code as a kind of cooking receipt and read it aloud, this can happen.
In fact it happened to a very experienced development co-worker just yesterday.

Related

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.)

PL/SQL - how to repeat alternating text for a given number of times with a final output?

I want to make a procedure called OE that will have text output based on the number that I define.
For example, inputting the number 6 will give the following output:
odd
even
odd
even
odd
even
= even steven!
and inputting the number 5 will give the following output:
odd
even
odd
even
odd
= you oddball!
I'm completely new at this and have been struggling to get the odd number to load correctly (for some reason, it gets stuck in an infinite loop). Any help would be appreciated! Here is what I got so far:
CREATE OR REPLACE procedure oe
(p_n IN number)
AS
v_n number;
v_on number;
BEGIN
v_n := p_n;
v_on := p_n;
IF v_n>0 THEN LOOP
dbms_output.put_line('odd');
v_n := v_n-1;
dbms_output.put_line('even');
v_n := v_n-1;
If v_n=0 then
exit;
if v_on mod 2 > 0 then dbms_output.put_line('=' || ' you oddball!');
exit;
else
dbms_output.put_line('=' || ' even steven!');
exit;
end if;
end if;
end loop;
end if;
END;
/
You are not using exit conditions properly hence your code is going in infinite loop. You simplify your logic as below. Let me know it it works for you.
You may add few validations to make sure you get proper input parameters such as p_n > 0 and other.
CREATE OR REPLACE procedure oe
(p_n IN number)
AS
begin
for i in 1..p_n
loop
if mod(i,2)=1 then dbms_output.put_line('odd');
else dbms_output.put_line('even');
end if;
end loop;
if mod(p_n,2)=1 then dbms_output.put_line('= you oddball!');
else dbms_output.put_line('= even steven!');
end if;
end;
hemalp108 has already answered this, but I just wanted to add that you don't even need the if/else logic that fills the procedure (except perhaps for handling values less than 1, which I'll leave as an exercise), because we have case:
create or replace procedure oe
( p_n in number )
as
begin
for i in 1 .. p_n loop
dbms_output.put_line(case mod(i,2) when 1 then 'odd' else 'even' end);
end loop;
dbms_output.put_line(case mod(p_n,2) when 1 then '= you oddball!' else '= even steven!' end);
end;
(You may also notice how laying your code out neatly is half the way towards debugging it.)

ORA-04021: timeout occurred while waiting to lock object

I have this anonymous PL/SQL block which calculates and prints a value return from a table.
DECLARE
U_ID NUMBER :=39;
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID INTO RETAIL, FLAG FROM UNITS WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID) INTO FLAG FROM UNITS WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN EXIT; END IF;
SELECT RETAIL* RETAIL_AMOUNT INTO RETAIL FROM UNITS WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
DBMS_OUTPUT.PUT_LINE( RETAIL);
END;
This block work correctly, but I wanted to do the same thing using a PL/SQL Function
I wrote the function as follow:
CREATE OR REPLACE FUNCTION GET_UNIT_RETAIL(U_ID NUMBER)
RETURN NUMBER
IS
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID
INTO RETAIL, FLAG
FROM UNITS
WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID)
INTO FLAG
FROM UNITS
WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN
EXIT;
END IF;
SELECT RETAIL* RETAIL_AMOUNT
INTO RETAIL
FROM UNITS
WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
RETURN NUMBER;
END;
/
When I try to execute the above code to save the function to the database, the environment (SQL*PLUS) hangs for a long time and at the end returns this error:
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
What is the problem ??? Please !
Sounds like ddl_lock problem
Take a look at
dba_ddl_locks to see who is "blocking" a create or replace.
Also try to create under different name - and see what happens.
The problem was because the Object GET_UNIT_RETAIL was busy by other environment
Here is the answer:
https://community.oracle.com/thread/2321256

Explain query that involves for loop with select statement and includes running of jobs

begin
for i in (select job from user_jobs where job=3309 and what like '%LEDGER_REPORT%')
loop
dbms_job.run(i.job);
end loop;
end;
/
commit;
begin
for i in (select job from user_jobs where job=3309 and what like '%LEDGER_REPORT%')
loop
dbms_job.broken(i.job,TRUE);
end loop;
end;
/
commit;
can someone explain how this works? i am new to this so I expect an explanation that is easy to understand for a beginner like me.
--start of the anon block
begin
-- selecting the rows that match this query (will likely only ever be one)
for i in (select job from user_jobs where job=3309 and what like '%LEDGER_REPORT%')
-- now we have 'i' which is a table record type, with 'job' as the only column/value
loop
-- for each record found, do dbms_job.run(). Pass in the value from our FOR loop
dbms_job.run(i.job);
end loop;
end;
/
commit;
-- commit makes sure the job is running
begin
-- same as above...
for i in (select job from user_jobs where job=3309 and what like '%LEDGER_REPORT%')
loop
-- now we set the job status to BROKEN, which means it won't be run again unless manually called
dbms_job.broken(i.job,TRUE);
end loop;
end;
/
commit;
It could use some optimization, a very round about way of doing things, but there it is. Feel free to ask for any other clarifications. The dbms_job procedures are Oracle packaged procedures, you can find more details on them online.

pl/sql loop records select oracle plsql

I have a select statement that I am trying to loop over and increment a variable based on the condition of the select statement, then return the variable as an out so I can do something with it in some front end code. I am using oracle 11g and I am seeing a few ways I can do this... but I am not sure which is the best way. I have some of what I am trying to do below, but again stopped because of confusion.
First I am setting my proc and 'in variable'
PROCEDURE SEEKER (pMonkeyID IN Number, vMarkCounter OUT Number)
AS
BEGIN
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc
OPEN seeker_cur;
begin
OPEN Seeker_cur;
vMarkCounter := 0;
Here is the part I am not sure about. Should I loop and then exit if the condition is not met or should I do an if statement and somehow determine if there is a record that could be greater than one? If so how would that work? Is there a benefit to doing one way over the other? So... I am going to sudo-code what I am trying to do (below):
FOR (however many records) in Seeker_cur
IF seeker_cur (not found) or (returns no records)
EXIT or (break for loop);
ELSE
LOOP
vMarkCounter := vMarkCounter + 1;
EXIT WHEN seeker_cur is out of records (somehow)
END IF;
END LOOP;
END;
END SEEKER;
I am sure there are a few ways to do this. What ways would you suggest?
why dont you use implicit cursor , it will open and close itself:
DECLARE
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc;
vMarkCounter number:=0;
BEGIN
FOR i IN seeker_cur
LOOP
vMarkCounter := vMarkCounter+1;
END LOOP;
dbms_output.put_line(vMarkCounter);
END;
It seems to me that the solution your problem might be as simple as this:
SELECT COUNT(*)
INTO l_some_local_variable
FROM monkey_book_apps
WHERE monkey_id = p_monkey_id
AND monkey_doc_type = 'BANANA';
RETURN l_some_local_variable;
Avoiding PL/SQL loops and using the simplest SQL possible is (almost always) the most efficient way. Tom Kyte calls the row-by-row execution of LOOPs "slow-by-slow".

Resources