query inside the exception and begin block in exception - sql

Hello I want to ask what is the best practise to do.
First example try to retrieve my data through exceptions I use this code in my main application and is working fine but I dont know if its good practise to code inside the exceptions blocks
BEGIN
DECLARE
v_status varchar2(100);
v_flag varchcar2(100);
BEGIN
SELECT STATUS INTO v_status FROM TABLE1 WHERE condition1;
EXCEPTION
when no_data_found then
select FLAG INTO v_flag FROM TABLE2 WHERE condition1; -- THERE WILL BE 100% RECORD
IF v_flag='N' THEN
V_STATUS:='N'
ELSIF v_flag:='O'
V_STATUS:='O'
ELSE
BEGIN
SELECT STATUS INTO V_STATUS FROM TABLE3 WHERE condition1,condition2;
EXCEPTION
V_STATUS:='F';
END;
END IF;
END;
IF V_STATUS='O' THEN
--DO SOMETHING HERE
ELSIF V_STATUS='N' THEN
--DO SOMETHING HERE
ELSE
--DO SOMETHING HERE
END IF;
END;
SECOND EXAMPLE TRY TO RETRIEVE DATA WITH CASES AND SELECT WITH COUNT.
BEGIN
DECLARE
V_CNTR NUMBER;
V_STATUS VARCHAR2(100);
BEGIN
SELECT COUNT(1) INTO V_CNTR FROM TABLE1 WHERE condition1;
CASE
WHEN COUNT=1 THEN
SELECT STATUS INTO V_STATUS FROM TABLE1 WHERE condition1;
ELSE
select FLAG INTO v_flag FROM TABLE2 WHERE condition1; -- THERE WILL BE 100% RECORD
IF v_flag='N' THEN
V_STATUS:='N'
ELSIF v_flag:='O'
V_STATUS:='O'
ELSE
SELECT COUNT(1) INTO V_CNTR FROM TABLE3 WHERE condition1,condition2;
CASE
WHEN count=1 THEN
SELECT STATUS INTO V_STATUS FROM TABLE3 WHERE condition1,condition2;
ELSE
V_STATUS:='F';
END CASE;
END IF;
END CASE;
END;
IF V_STATUS='O' THEN
--DO SOMETHING HERE
ELSIF V_STATUS='N' THEN
--DO SOMETHING HERE
ELSE
--DO SOMETHING HERE
END IF;
END;

From personal experience...
Those nested blocks work fine and it can be useful to use code in exception blocks, but it becomes very unreadable quickly as you show in your example. If your initial code looks like this, then imagine what it will look like a couple of development cycles later.
It is cleaner to move those BEGIN SELECT INTO EXCEPTION WHEN NO_DATA_FOUND THEN... END: blocks into functions. Makes the code a lot more structured, more readable and easier to debug and maintain:
DECLARE
v_status varchar2(100);
v_flag varchcar2(100);
FUNCTION status (argument_i VARCHAR2) RETURN VARCHAR2
IS
l_status VARCHAR2(100);
BEGIN
SELECT STATUS INTO v_status FROM TABLE1 WHERE condition = argument_i;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN NULL; -- or -1 or NOTFOUND - whatever you prefer
END;
BEGIN
v_status := status(argument_i => condition);
IF v_status IS NULL THEN
...
ELSE
...
END IF;
END;
Here it's an inline function - within packages you can use standalone functions, private if never called outside the package.
Note, in your 2 examples you declare the variables in the inner block but call them in the outer block - that is something to avoid.

The best practice is always to keep your code clean and readable.
To keep your code clean and readable, make it well structured.
To make it well structured, you have to split code in units where each unit does one thing - has one responsibility.
Read more on SOLID principles, DRY principle to get the idea.
Short answer to your question: coding in exception blocks is not the best practice I'd follow.
Take a look at this presentation on clean PL/SQL coding to get some greater overview.

Related

Cannot rollback while a subtransaction is active - Error 2D000

I have written a stored procedure that basically loops over an array of fields and performs some manipulation in the db for each iteration. What I want to achieve is, either all the iterations of loops should occur or neither one of them should occur.
So let's say there were 5 elements in the fields array and the loop iterates up to the 3rd element before noticing that some condition is true and throwing an error, I want to rollback all the changes that occurred during the first 2 iterations. I've used ROLLBACK statements to achieve the same, but every time it reaches the ROLLBACK statement it throws the following error:
Cannot rollback while a subtransaction is active : 2D000
Surprisingly, it works as normal if I comment out the outobj := json_build_object('code',0); statement within the EXCEPTION WHEN OTHERS THEN block or if I remove that whole block completely.
I've checked the PostgreSQL documentation for error codes, but it didn't really help. My stored procedure is as follows:
CREATE OR REPLACE PROCEDURE public.usp_add_fields(
field_data json,
INOUT outobj json DEFAULT NULL::json)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
v_user_id bigint;
farm_and_bussiness json;
_field_obj json;
_are_wells_inserted boolean;
BEGIN
-- get user id
v_user_id = ___uf_get_user_id(json_extract_path_text(field_data,'user_email'));
IF(v_user_id IS NULL) THEN
outobj := json_build_object('code',17);
RETURN;
END IF;
-- Loop over entities to create farms & businesses
FOR _field_obj IN SELECT * FROM json_array_elements(json_extract_path(field_data,'fields'))
LOOP
-- check if irrigation unit id is already linked to some other field
IF(SELECT EXISTS(
SELECT field_id FROM user_fields WHERE irrig_unit_id LIKE json_extract_path_text(_field_obj,'irrig_unit_id') AND deleted=FALSE
)) THEN
outobj := json_build_object('code',26);
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
-- check if this field name already exists
IF( SELECT EXISTS(
SELECT uf.field_id FROM user_fields uf
INNER JOIN user_farms ufa ON (ufa.farm_id=uf.user_farm_id AND ufa.deleted=FALSE)
INNER JOIN user_businesses ub ON (ub.business_id=ufa.user_business_id AND ub.deleted=FALSE)
INNER JOIN users u ON (ub.user_id = u.user_id AND u.deleted=FALSE)
WHERE u.user_id = v_user_id
AND uf.field_name LIKE json_extract_path_text(_field_obj,'field_name')
AND uf.deleted=FALSE
)) THEN
outobj := json_build_object('code', 22);
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
--create/update user business and farm and return farm_id
CALL usp_add_user_bussiness_and_farm(
json_build_object('user_email', json_extract_path_text(field_data,'user_email'),
'business_name', json_extract_path_text(_field_obj,'business_name'),
'farm_name', json_extract_path_text(_field_obj,'farm_name')
), farm_and_bussiness);
IF(json_extract_path_text(farm_and_bussiness, 'code')::int != 1) THEN
outobj := farm_and_bussiness;
-- Rollback any changes made by previous iterations of loop
ROLLBACK;
RETURN;
END IF;
-- insert into users fields
INSERT INTO user_fields (user_farm_id, irrig_unit_id, field_name, ground_water_percent, surface_water_percent)
SELECT json_extract_path_text(farm_and_bussiness,'farm_id')::bigint,
json_extract_path_text(_field_obj,'irrig_unit_id'),
json_extract_path_text(_field_obj,'field_name'),
json_extract_path_text(_field_obj,'groundWaterPercentage'):: int,
json_extract_path_text(_field_obj,'surfaceWaterPercentage'):: int;
-- add to user wells
CALL usp_insert_user_wells(json_extract_path(_field_obj,'well_data'), v_user_id, _are_wells_inserted);
END LOOP;
outobj := json_build_object('code',1);
RETURN;
EXCEPTION WHEN OTHERS THEN
raise notice '% : %', SQLERRM, SQLSTATE;
outobj := json_build_object('code',0);
RETURN;
END;
$BODY$;
If you have an EXCEPTION clause in a PL/pgSQL block, that whole block will be executed in a subtransaction that is rolled back when an exception happens. So you cannot use COMMIT or ROLLBACK in such a block.
If you really need that ROLLBACK, rewrite your code like this:
DECLARE
should_rollback boolean := FALSE;
BEGIN
FOR ... LOOP
BEGIN -- inner block for exception handling
/* do stuff */
IF (/* condition that should cause a rollback */) THEN
should_rollback := TRUE;
EXIT; -- from LOOP
END IF;
EXCEPTION
WHEN OTHERS THEN
/* handle the error */
END;
END LOOP;
IF should_rollback THEN
ROLLBACK;
/* do whatever else is needed */
END IF;
END;
Now the rollback does not happen in a block with an exception handler, and it should work the way you want.
Explanation:
Based on the clue provided by #Laurez Albe, I came up with a cleaner way to solve the above problem.
Basically, what I've done is, I've raised a custom exception whenever a condition is true. So when an exception is thrown, all the changes made by block X are rolled back gracefully. I can even perform last minute cleanup within the exception conditional blocks.
Implementation:
CREATE OR REPLACE procedure mProcedure(INOUT resp json DEFAULT NULL::JSON)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
field_data json := '{ "fields": [1,2,3,4,5] }';
_field_id int;
BEGIN
-- Start of block X
FOR _field_id IN SELECT * FROM json_array_elements(json_extract_path(field_data,'fields'))
LOOP
INSERT INTO demo VALUES(_field_id);
IF(_field_id = 3) THEN
RAISE EXCEPTION USING ERRCODE='22013';
END IF;
IF(_field_id = 5) THEN
RAISE EXCEPTION USING ERRCODE='22014';
END IF;
END LOOP;
SELECT json_agg(row_to_json(d)) INTO resp FROM demo d;
RETURN;
-- end of block X
-- if an exception occurs in block X, then all the changes made within the block are rollback
-- and the control is passed on to the EXCEPTION WHEN OTHERS block.
EXCEPTION
WHEN sqlstate '22013' THEN
resp := json_build_object('code',26);
WHEN sqlstate '22014' THEN
resp := json_build_object('code',22);
END;
$BODY$;
Demo:
Dbfiddle

Why does my explicit cursor fetch only specific rows from my database in PL/SQL?

I am very new to PL/SQL and I am trying to use an explicit cursor to iterate over my database, FLEX_PANEL_INSPECTIONS. I would like to fetch each row from the database in turn using an explicit cursor, and depending on the randomly generated 'status' of a given 'panel' in the row, assign the panel a new status value within an if / else statement. The status of the panel is random but Boolean - it is either 1 or 0.
However, when I view the DBMS output, I note that the fetch does not retrieve all values from the database - only those who have a status value of 1. I have included the core code below.
I would be very grateful if anybody is able to help me find a solution, or explain the root cause of my problem, thanks!
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
old_panel_status number;
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
open panel_cursor;
loop
fetch panel_cursor into old_panel_status;
exit when panel_cursor%notfound;
if old_panel_status = 0
then new_panel_status := 2;
elsif old_panel_status = 1
then new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question).
dbms_output.put_line(old_panel_status);
--Test output
--This displays all of the 1's that were randomly generated in the original table.
--It does not display any of the 0's that were generated.
end if;
end loop;
close panel_cursor;
close sensor_cursor;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/
In addition to the main error, which has already been fixed in the accepted answer, the below code shows the newer loop construct. Instead of rec, you can choose any variable name you like. On each iteration, it contains a row (usually with more than one column).
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
for rec in panel_cursor loop
if rec.flex_panel_status = 0 then
new_panel_status := 2;
elsif rec.flex_panel_status = 1 then
new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question)
end if;
dbms_output.put_line(rec.flex_panel_status);
end loop;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/
You can even get rid if the explicit cursor if you like:
for rec in (
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS
) loop
If you didn't make a mistake when removing the additional elseif clauses, the issue is in the location of your dbms_output.put_line.
It's located inside the else part, so will only trigger when this clause is called. Move it below the END IF and make sure to use proper indentation, which makes such things way easier to spot.
create or replace procedure FLEX_SUMMARY_STATUS_PROCEDURE as
old_panel_status number;
new_panel_status number;
cursor panel_cursor is
select FLEX_PANEL_STATUS
from FLEX_PANEL_INSPECTIONS;
begin
open panel_cursor;
loop
fetch panel_cursor into old_panel_status;
exit when panel_cursor%notfound;
if old_panel_status = 0
then new_panel_status := 2;
elsif old_panel_status = 1
then new_panel_status := 3;
--More conditional loops follow (but are irrelevant for this question)
end if;
dbms_output.put_line(old_panel_status);
end loop;
close panel_cursor;
close sensor_cursor;
end FLEX_SUMMARY_STATUS_PROCEDURE;
/

Exception in CURSOR or other solution

I have a DB where selling tickets. I have such procedure, where I count all sold money from some race:
CREATE OR REPLACE PROCEDURE Total_money(depart IN RACE.DEPART_PLACE%TYPE,
dest IN RACE.DESTINATION_PLACE%TYPE, total OUT TICKET.PRICE%TYPE)
IS
CURSOR tickets
IS SELECT t.CLIENT_ID, t.PRICE FROM TICKET t JOIN VAGON v ON t.VAGON_ID = v.VAGON_ID
JOIN RACE r ON v.RACE_ID = r.RACE_ID
WHERE r.DEPART_PLACE = depart AND r.DESTINATION_PLACE = dest;
BEGIN
FOR t IN tickets
LOOP
IF t.CLIENT_ID IS NOT NULL THEN
total := total + t.PRICE;
END IF;
END LOOP;
END;
First question: Can I place an exception into CURSOR declaration? Or what can I do, when I pass wrong depart name or destination name of the train? Or these names don't exist in DB. Then it will create an empty cursor. And return 0 money. How to control this?
Second question: After procedure declaration, I run these commands:
DECLARE t TICKET.PRICE%TYPE;
t:=0;
execute total_money('Kyiv', 'Warsaw', t)
But there is an error(PLS-00103 Encountered the symbol...)
First question: How to fix it?
A simple check is just to test that total is non-zero after the loop:
...
END LOOP;
IF total <= 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Toal zero, invalid arguments?');
END IF;
END;
If the total could legitimately be zero (which seems unlikely here, apart from the client ID check) you could have a counter of a flag and check that:
CREATE ... IS
found BOOLEAN := false;
CURSOR ...
BEGIN
total := 0;
FOR t IN tickets
LOOP
found := true;
IF t.CLIENT_ID IS NOT NULL THEN
total := total + t.PRICE;
END IF;
END LOOP;
IF NOT found THEN
RAISE_APPLICATION_ERROR(-20001, 'No records, invalid arguments?');
END IF;
END;
execute is an SQL*Plus command, so I'm not sure which way you want this to work. You can use an anonymous block like this:
DECLARE
t TICKET.PRICE%TYPE;
BEGIN
total_money('Kyiv', 'Warsaw', t);
-- do something with t
END;
/
Or using an SQL*Plus (or SQL Developer) variable you can do:
variable t number;
execute total_money('Kyiv', 'Warsaw', :t);
print t
I'd change it from a procedure to a function though; declare a total within it, initialise it to zero, and return that instead of having an out parameter. Then you can call it from PL/SQL or from SQL, within a simple select.
And as ElectricLlama points out, you don't need a cursor; and don't need to do this in PL/SQL at all - just use an aggregate sum(). I assume this is an exercise to learn about cursors though?

When should I nest PL/SQL BEGIN...END blocks?

I've been somewhat haphazardly grouping subsections of code in BEGIN...END blocks when it seems right. Mostly when I'm working on a longer stored procedure and there's a need for a temporary variable in one spot I'll declare it just for that portion of the code. I also do this when I want to identify and handle exceptions thrown for a specific piece of code.
Any other reasons why one should nest blocks within a procedure, function or another larger block of PL/SQL?
When you want to handle exceptions locally like this:
begin
for emp_rec in (select * from emp) loop
begin
my_proc (emp_rec);
exception
when some_exception then
log_error('Failed to process employee '||emp_rec.empno);
end;
end loop;
end;
In this example, the exception is handled and then we carry on and process the next employee.
Another use is to declare local variables that have limited scope like this:
declare
l_var1 integer;
-- lots of variables
begin
-- lots of lines of code
...
for emp_rec in (select * from emp) loop
declare
l_localvar integer := 0;
begin
-- Use l_localvar
...
end
end loop;
end;
Mind you, wanting to do this is often a sign that your program is too big and should be broken up:
declare
l_var1 integer;
-- lots of variables
...
procedure local_proc (emp_rec emp%rowtype):
l_localvar integer := 0;
begin
-- Use l_localvar
...
end
begin
-- lots of lines of code
...
for emp_rec in (select * from emp) loop
local_proc (emp_rec);
end loop;
end;
I tend to nest blocks when I want to create procedures that are specific to data that only exists within the block. Here is a contrived example:
BEGIN
FOR customer IN customers LOOP
DECLARE
PROCEDURE create_invoice(description VARCHAR2, amount NUMBER) IS
BEGIN
some_complicated_customer_package.create_invoice(
customer_id => customer.customer_id,
description => description,
amount => amount
);
END;
BEGIN
/* All three calls are being applied to the current customer,
even if we're not explicitly passing customer_id.
*/
create_invoice('Telephone bill', 150.00);
create_invoice('Internet bill', 550.75);
create_invoice('Television bill', 560.45);
END;
END LOOP;
END;
Granted, it's not usually necessary, but it has come in really handy when a procedure can be called from many locations.
One reason to have nested BEGIN/END blocks is to be able to handle exceptions for a specific local section of the code and potentially continue processing if the exception is processed.

Oracle PL/SQL - Are NO_DATA_FOUND Exceptions bad for stored procedure performance?

I'm writing a stored procedure that needs to have a lot of conditioning in it. With the general knowledge from C#.NET coding that exceptions can hurt performance, I've always avoided using them in PL/SQL as well. My conditioning in this stored proc mostly revolves around whether or not a record exists, which I could do one of two ways:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
-or-
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
The second case seems a bit more elegant to me, because then I can use NEEDED_FIELD, which I would have had to select in the first statement after the condition in the first case. Less code. But if the stored procedure will run faster using the COUNT(*), then I don't mind typing a little more to make up processing speed.
Any hints? Am I missing another possibility?
EDIT
I should have mentioned that this is all already nested in a FOR LOOP. Not sure if this makes a difference with using a cursor, since I don't think I can DECLARE the cursor as a select in the FOR LOOP.
I would not use an explicit cursor to do this. Steve F. no longer advises people to use explicit cursors when an implicit cursor could be used.
The method with count(*) is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
The second version from the original post does not have this problem, and it is generally preferred.
That said, there is a minor overhead using the exception, and if you are 100% sure the data will not change, you can use the count(*), but I recommend against it.
I ran these benchmarks on Oracle 10.2.0.1 on 32 bit Windows. I am only looking at elapsed time. There are other test harnesses that can give more details (such as latch counts and memory used).
SQL>create table t (NEEDED_FIELD number, COND number);
Table created.
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
1 row created.
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.06
Since SELECT INTO assumes that a single row will be returned, you can use a statement of the form:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
The SELECT will give you the value if one is available, and a value of NULL instead of a NO_DATA_FOUND exception. The overhead introduced by MAX() will be minimal-to-zero since the result set contains a single row. It also has the advantage of being compact relative to a cursor-based solution, and not being vulnerable to concurrency issues like the two-step solution in the original post.
An alternative to #Steve's code.
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
The loop is not executed if there is no data. Cursor FOR loops are the way to go - they help avoid a lot of housekeeping. An even more compact solution:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
Which works if you know the complete select statement at compile time.
#DCookie
I just want to point out that you can leave off the lines that say
EXCEPTION
WHEN OTHERS THEN
RAISE;
You'll get the same effect if you leave off the exception block all together, and the line number reported for the exception will be the line where the exception is actually thrown, not the line in the exception block where it was re-raised.
Stephen Darlington makes a very good point, and you can see that if you change my benchmark to use a more realistically sized table if I fill the table out to 10000 rows using the following:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
Then re-run the benchmarks. (I had to reduce the loop counts to 5000 to get reasonable times).
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
The method with the exception is now more than twice as fast. So, for almost all cases,the method:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
is the way to go. It will give correct results and is generally the fastest.
If it's important you really need to benchmark both options!
Having said that, I have always used the exception method, the reasoning being it's better to only hit the database once.
Yes, you're missing using cursors
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
admittedly this is more code, but it doesn't use EXCEPTIONs as flow-control which, having learnt most of my PL/SQL from Steve Feuerstein's PL/SQL Programming book, I believe to be a good thing.
Whether this is faster or not I don't know (I do very little PL/SQL nowadays).
Rather than having nested cursor loops a more efficient approach would be to use one cursor loop with an outer join between the tables.
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
you dont have to use open when you are using for loops.
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
or
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
May be beating a dead horse here, but I bench-marked the cursor for loop, and that performed about as well as the no_data_found method:
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.18
The count(*) will never raise exception because it always returns actual count or 0 - zero, no matter what. I'd use count.
The first (excellent) answer stated -
The method with count() is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
Not so. Within a given logical Unit of Work Oracle is totally consistent. Even if someone commits the delete of the row between a count and a select Oracle will, for the active session, obtain the data from the logs. If it cannot, you will get a "snapshot too old" error.