What is the difference between BEGIN followed by IF and IF followed by BEGIN - oracle

I am new to PL/SQL. What is the difference between the below two flows?
BEGIN followed by IF
BEGIN
IF flag= 1 THEN
FOR SOME_REC(id)
LOOP
END LOOP;
END IF;
END
IF followed by BEGIN
IF flag= 1 THEN
BEGIN
FOR SOME_REC(id)
LOOP
END LOOP;
END
END IF;

By its design PL/SQL is a block structured language with each block consisting of 4 components:
Declaration Section - optional:; DECLARE or Named Block (Function, Procedure, ...)
Execution Section - required: BEGIN
Exception Section - optional: EXCEPTION
End Statement - required: END
As #Mr.AF indicates each block defines the SCOPE of variables. Further these blocks can be nested. So a variable declared in an outer block is visible within that block and all blocks nested within. Subject to the rule that closest declaration wins and items declared in an nested bloc are not visible in outer blocks. See GURU99 for fuller description. Try the following.
declare
var varchar2(5) := '12345';
begin
dbms_output.put_line ( 'Outer block has variable ''var'' and has a length of '
|| to_char(length(var)) || ' and value of ' || var);
declare -- start nested block
var varchar2(8);
inner_only varchar2(8) := 'ShotTerm';
begin
var := '12345678';
dbms_output.put_line ( 'Inner block has variable ''var'' and has a length of '
|| to_char(length(var)) || ' and value of ' || var
|| ' and also variable inner_only with value ' || inner_only);
end ; -- end nested block
dbms_output.put_line ( 'Outer block variable ''var'' still has a length of '
|| to_char(length(var)) || ' and value of ' || var);
-- uncomment below to see difference
-- dbms_output_put_line('A reference to ' || inner_only || ' does not work; gives compile error');
end ; --outer block
For your specific scenarios #1 the IF and LOOP are being run in an outer (or only) block. While #2 the IF is run in the outer block while the loop runs in an inner block. Note: #2 must have a prior BEGIN and the END IF must be followed by an END - without them you will get a compile syntax error.

BEGIN ... END keywords implies starting point and end point of a code block like { } and compiler will validate local references (locality of references) within the block.otherwise, unexpected results may be happened.

The way you put it, without any further explanation of what problem you are trying to solve (if any), then there's no much difference in those two pieces of code. Both will do what they are supposed to do.
However, I'd like to say something about the 3rd case you never mentioned: BEGIN-EXCEPTION-END block within the loop, which does make the difference.
IF flag = 1 THEN
FOR SOME_REC(id) LOOP
BEGIN
do_something_here;
EXCEPTION
when some exception then
handle it
END;
END LOOP;
END IF;
What would be its purpose? In both of your examples, if any exception occurs within the loop, processing will be stopped and the procedure will terminate.
But, if there's an inner begin-exception-end block, it lets you capture an error within the loop, handle it the way you find the most appropriate (if there's nothing better, just log the error) and move on to the next loop cycle.
Doing so, you'll be able to process all records returned by the cursor. Some of them might fail, that's true, but you'll be able to proceed with the execution.
Here's an example based on Scott's EMP table; I'll try to divide SAL and NVL(COMM, 0) which will result in divide by zero error in some cases.
SQL> select ename, sal, nvl(comm, 0) comm from emp where deptno = 30 order by ename;
ENAME SAL COMM
---------- ---------- ----------
ALLEN 1600 300
BLAKE 2850 0 --> 2850/0 --> error
JAMES 950 0 --> error
MARTIN 1250 1400
TURNER 1500 0 --> error
WARD 1250 500
6 rows selected.
Code, as you wrote it:
SQL> set serveroutput on
SQL> declare
2 l_divided number;
3 begin
4 for cur_r in (select ename, sal, nvl(comm, 0) comm
5 from emp
6 where deptno = 30
7 order by ename
8 )
9 loop
10 l_divided := round(cur_r.sal / cur_r.comm);
11 dbms_output.put_line(cur_r.ename ||': ' || l_divided);
12 end loop;
13 end;
14 /
ALLEN: 5
declare
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 10
SQL>
See? Only the first one, Allen, had valid data. The rest of them weren't even calculated, as soon as the first error appeared.
But, if you rewrite it as I suggested, then all of them are processed:
SQL> declare
2 l_divided number;
3 begin
4 for cur_r in (select ename, sal, nvl(comm, 0) comm
5 from emp
6 where deptno = 30
7 order by ename
8 )
9 loop
10 begin
11 l_divided := round(cur_r.sal / cur_r.comm);
12 dbms_output.put_line(cur_r.ename ||': ' || l_divided);
13 exception
14 when zero_divide then
15 dbms_output.put_line(cur_r.ename ||': COMM is equal to zero');
16 end;
17 end loop;
18 end;
19 /
ALLEN: 5
BLAKE: COMM is equal to zero
JAMES: COMM is equal to zero
MARTIN: 1
TURNER: COMM is equal to zero
WARD: 3
PL/SQL procedure successfully completed.
SQL>
See if the above makes any sense in what you asked.

Related

For loop not printing last record in a cursor

So,I am working with a cursor,the cursor was initially operating with the initial for loop,now i needed to perform operation if the no of records in the cursor is more than one,so i fetched the no of records first and stored in a variable and used an if-condition based on that.Now the problem is when I run the whole process,the procedure does its job,but only for the first record in the cursor and entirely skips the second record.Please suggest or help me identify the mistake.
Adding code snippet.
for m in get_m_p(a,b)--main cursor
loop
fetch get_m_p into c_m;
exit when g_m_p%notfound;
end loop;
tempcount := g_m_p%ROWCOUNT:
statements---
if(tempcount>1) then
statements----
end if;
end loop;
for the two records the main curosr is returning in first line,operations are only done for the first one,and the second record is being skipped entirely.
This is a superfluous line:
fetch get_m_p into c_m;
You don't explicitly fetch within a cursor FOR loop, it is implicitly done in each loop iteration. Remove that line.
How to get number of rows returned by a cursor? Lucky you, it seems that you don't care whether how many rows - exactly - it returns. All you want to know is whether it returned more than 1 row. So, count them, literally; exit the loop if counter exceeds 1.
SQL> DECLARE
2 CURSOR get_m_p IS
3 SELECT *
4 FROM emp
5 WHERE deptno = 10;
6
7 l_cnt NUMBER := 0;
8 BEGIN
9 FOR m IN get_m_p
10 LOOP
11 l_cnt := l_cnt + 1;
12 EXIT WHEN l_cnt > 1;
13 END LOOP;
14
15 DBMS_OUTPUT.put_line ('Cursor returned at least ' || l_cnt || ' row(s)');
16
17 IF l_cnt > 1
18 THEN
19 NULL;
20 -- the rest of statements go here
21 END IF;
22 END;
23 /
Cursor returned at least 2 row(s)
PL/SQL procedure successfully completed.
SQL>
As there's no way to know how many rows will cursor return, unfortunately, you'll have to check that first, and then decide what to do with the result.
DECLARE
CURSOR get_m_p IS
SELECT *
FROM emp
WHERE deptno = 10;
l_cnt NUMBER := 0;
BEGIN
SELECT COUNT (*)
INTO l_cnt
FROM (-- this is cursor's SELECT statement
SELECT *
FROM emp
WHERE deptno = 10);
FOR m IN get_m_p
LOOP
-- some statements here
IF l_cnt > 1
THEN
NULL;
-- statements to be executed if cursor return more than 1 row
END IF;
END LOOP;
END;
/
Cursor:
Oracle creates memory area to process SQL statement which is called context area and the cursor is pointer to the context area. A cursor holds the rows (one or more) returned by a SQL statement. The set of rows the cursor holds is referred to as the active set.
There are two type of cursor
1. Implicit cursor
2. Explicit cursor
Implicit Cursors :
Implicit cursors are automatically created by Oracle whenever an SQL statement is executed. Any SQL cursor attribute will be accessed as sql%attribute_name as shown below in the example. Use the SQL%ROWCOUNT attribute to determine the number of rows affected
DECLARE
no_of_records number(2);
BEGIN
select * from records;
IF sql%notfound THEN
dbms_output.put_line('no records present');
ELSIF sql%found THEN
no_of_records := sql%rowcount;
IF no_of_records > 1 THEN
dbms_output.put_line('no of records ' || no_of_records);
END IF
END IF;
END;
Explicit Cursors :
Explicit cursors are programmer-defined cursors for gaining more control over the context area. An explicit cursor should be defined in the declaration section of the PL/SQL Block. It is created on a SELECT Statement which returns more than one row.
Please see below example:
DECLARE
r_id records.id%type;
CURSOR c_records is
SELECT id FROM records;
BEGIN
OPEN c_records;
LOOP
FETCH c_records into r_id;
EXIT WHEN c_records%notfound;
dbms_output.put_line('Record id ' || r_id );
END LOOP;
CLOSE c_records;
END;
Reference :
https://www.tutorialspoint.com/plsql/plsql_cursors.htm
As an alternative you can cache every row and process after.
Example using sample schema "HR" on Oracle 11g Express Edition:
DECLARE
CURSOR get_m_p
IS
SELECT *
FROM hr.employees
WHERE department_id = 60
order by employee_id;
--
rcEmp_last get_m_p%rowtype;
l_cnt NUMBER;
BEGIN
FOR rcM IN get_m_p LOOP
l_cnt := get_m_p%rowcount;
Dbms_Output.Put_Line('l_cnt='||l_cnt);
if l_cnt=1 then
rcEmp_last:=rcM;
Else
Dbms_Output.Put_Line('Process='||to_char(l_cnt-1));
Dbms_Output.Put_Line('rcEmp_last.employee_id='||rcEmp_last.employee_id);
--
rcEmp_last:=rcM;
END IF;
End loop;
--
Dbms_Output.Put_Line('Exited FOR-LOOP');
Dbms_Output.Put_Line('l_cnt='||l_cnt);
--
if l_cnt>1 then
Dbms_Output.Put_Line('rcEmp_last.employee_id='||rcEmp_last.employee_id);
End if;
END;
Output:
Text
PL/SQL block, executed in 1 ms
l_cnt=1
l_cnt=2
Process=1
rcEmp_last.employee_id=103
l_cnt=3
Process=2
rcEmp_last.employee_id=104
l_cnt=4
Process=3
rcEmp_last.employee_id=105
l_cnt=5
Process=4
rcEmp_last.employee_id=106
Exited FOR-LOOP
l_cnt=5
rcEmp_last.employee_id=107
Total execution time 35 ms

Control Loop Statement from Exception Block Oracle

I want to know if there is any way in oracle to redirect the control from exception block to the current begin/end block.
Following is the code snippet. i variable loops through the table data sets, I want to update table i; if there is any exception go to the exec1 exception block,do the stuff and then try updating table i again.
If control goes to the exec2 exception then continue to the next iteration.
I want help as after performing steps in exec1 exception, how can I try updating table(i) again; so that if it fails again it is handled in exec1 exception block.
FOR i in c_tables
LOOP
BEGIN
---label---
Update (i);
DBMS_OUTPUT.PUT_LINE ('ROWS UPDATED IS: ' || SQL%ROWCOUNT);
EXCEPTION
WHEN exec1 THEN
BEGIN
do_stuff();
goto label;
EXCEPTION WHEN exec2 THEN
do_stuff();
continue;
END;
END;
END LOOP;
I tried to simulate what you have, in Scott's schema.
It seems that you'd be good if you moved the label in front of the BEGIN, not after it. Have a look:
SQL> DECLARE
2 l_deptno_dflt NUMBER := 50;
3 BEGIN
4 FOR i IN (SELECT distinct deptno FROM emp)
5 LOOP
6 <<this_is_label>> -- put label here ...
7 BEGIN
8 -- <<this_is_label>> -- ... not here
9 UPDATE emp
10 SET deptno = l_deptno_dflt
11 WHERE deptno = i.deptno;
12
13 DBMS_OUTPUT.put_line ('rows updated is ' || SQL%ROWCOUNT);
14 EXCEPTION
15 WHEN OTHERS
16 THEN
17 DBMS_OUTPUT.put_line ('this is exec1');
18
19 BEGIN
20 l_deptno_dflt := 40;
21 goto this_is_label;
22 END;
23 END;
24 END LOOP;
25 END;
26 /
this is exec1
rows updated is 6
rows updated is 5
rows updated is 3
PL/SQL procedure successfully completed.
SQL>
If it is where you initially put it, it raises the mentioned PLS-00375 error.

PL/SQL IF statement and variable declaration

I am trying to make the code posted below work, but I am receiving an error about the IF statement:
DECLARE
a number;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
EXCEPTION
WHEN no_data_found THEN a := 0;
END;
IF (a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
In the first block I am declaring an 'A' variable and setting it to 0 (by the way, the API call result will be 0 or 1, nothing else). The first block works fine or - at least - I do think so. But the IF block is not working and having this error:
PLS-00103:Encountered the symbol "IF"
Any help is appreciated.
Update: I have tried it this way:
DECLARE
a number:=0;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
IF (:a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
END;
I received the exception below:
ORA-01008: not all variables bound
Update
In other words, what I am trying to do is:
if Pers_API.Is_Manager_(10018) returns true (as 1), I have to do another select statement
if it's false (as 0), I have to return null.
Any other ideas are appreciated.
A few objections, if I may.
there's no need to select from dual in order to assign a value to a variable, when that value is returned by a function - simply assign it
exception handler seems to be superfluous. You said that the is_manager_ function returns 0 or 1 and nothing else. Therefore, it'll always return something, so - why do you expect that SELECT to return NO_DATA_FOUND? Besides, such a case should be handled by the function itself (i.e. you have to make sure that it returns 0 or 1 and nothing else)
the last paragraph you wrote is somewhat strange. You said that - if the function returns 1, you have to run another SELECT statement. Otherwise, you have to return null (while code you wrote, select 0 ... suggests a zero (0), not null); so, which one is true?
also, saying that you have to return something (null, 0, whatever) suggests that piece of code you wrote should be a function that returns a value. Is that so?
Code that actually compiles might look like in this example.
First, a function (based on Scott's schema) that checks whether an employee is a manager:
SQL> create or replace function is_manager_ (par_empno in number)
2 return number
3 is
4 /* function checks whether PAR_EMPNO belongs to a manager and returns:
5 - 1 - employee is a manager
6 - 0 - employee is NOT a manager
7 */
8 retval number := 0;
9 begin
10 select max(1)
11 into retval
12 from emp e
13 where mgr = par_empno;
14
15 return retval;
16 exception
17 when no_data_found then
18 return retval;
19 end;
20 /
Function created.
SQL>
Code you posted, rewritten so that it does something:
if an employee is a manager, returns his/her salary
otherwise, it does not
SQL> declare
2 a number := is_manager_(&&l_empno);
3 l_sal emp.sal%type;
4 begin
5 if a > 0 then
6 select sal
7 into l_sal
8 from emp
9 where empno = &&l_empno;
10 dbms_output.put_line('Employee is a manager and his/her salary is ' || l_sal);
11 else
12 null;
13 dbms_output.put_line('Employee is not a manager');
14 end if;
15 end;
16 /
Enter value for l_empno: 7698
Employee is a manager and his/her salary is 2850
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: 7369
Employee is not a manager
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: -1
Employee is not a manager
PL/SQL procedure successfully completed.
SQL>
See if you can adjust it so that it fits your needs. If not, do answer to my objections posted at the beginning of this message, and we'll see what to do next.
It appears to me that what you are really trying to achieve here is to run a query which must use the result obtained from the package function as a condition. If so, the PL/SQL block wouldn't be needed and can be written as a basic select operation.
What is still not clear from your explanation is whether your select query
SELECT 1 from dual is the actual query you are about to use in your code.Generally, in the select statement, we use CASE expression to evaluate the conditional logic, which essentially does what IF ELSE condition was supposed to do to return the desired result.The query would have a structure of this form,
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
END
as
FROM DUAL;
More specifically, if you want to have the resultant expression come from another database table, the queries can take the form
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
as
FROM table_to_run_query
WHERE where_clauses = some_values;
If you say that your resultant expression from the table involves many other constructs, the challenge would be to compose it in a single query cleverly to avoid any PL/SQL at all. That would be achievable, but it will require that you explain us with some sample data and expected result, as to what exactly you want,preferably in a new question.
It would not be complete if I don't mention the REFCURSOR technique to obtain results from a select query in PL/SQL. It is either through DBMS_SQL.RETURN_RESULT(Oracle 12c and above) or using a REFCURSOR bind variable running it in SQL* Plus or as Script in other tools (F5).
VARIABLE x refcursor;
DECLARE BEGIN
IF
pers_api.is_manager_(10018) = 1
THEN
OPEN :x FOR SELECT some_columns FROM some_tables;
ELSE
OPEN :x FOR SELECT some_columns FROM other_tables;
END IF;
--DBMS_SQL.RETURN_RESULT(:x); --12c and above
END;
/
PRINT x -- 11g

asking for user input in PL/SQL

I am new to PL/SQL and I am stuck on some code.
I am wanting to ask the user for a number and then I want to be able to use this number in an IF THEN statement to verify if the number is greater than or less than 20.
I am stuck on how to obtain user input and once I have it, I can store it and give it back to the user and verify its greater than or less than 20.
DECLARE
a number(2) := 10;
BEGIN
a:= 10;
-- check the boolean condition using if statement
IF( a < 20 ) THEN
-- if condition is true then print the following
dbms_output.put_line('a is less than 20 ' );
END IF;
dbms_output.put_line('value of a is : ' || a);
END;
/
SQL> set serveroutput on; -- for "dbms_output.put_line" to take effect
SQL> DECLARE
a number := &i_nr; -- there's no need to restrict a non-decimal numeric variable to a length
BEGIN
--a:= 10; --no need this when initialization is in declaration section
-- check the boolean condition using if statement
IF( a < 20 ) THEN
-- if condition is true then print the following
dbms_output.put_line('a is less than 20 ' );
END IF;
dbms_output.put_line('value of a is : ' || a);
END;
/
-- it prompts you for value of &i_nr "enter a numeric value, for example 10", for string values it must be in quotes '&i_str'
In SQLPlus, you'd use "&"; in my example, it is && so that I wouldn't have to enter the same value several times (i.e. every time "a" is referenced):
SQL> begin
2 if &&a < 20 then
3 dbms_output.put_line('a is less than 20');
4 end if;
5 dbms_output.put_line('value of a is: ' || &&a);
6 end;
7 /
Enter value for a: 5
old 2: if &&a < 20 then
new 2: if 5 < 20 then
old 5: dbms_output.put_line('value of a is: ' || &&a);
new 5: dbms_output.put_line('value of a is: ' || 5);
a is less than 20
value of a is: 5
PL/SQL procedure successfully completed.
SQL>
Though, I'd say that you'd rather create a procedure with an IN parameter, such as
SQL> create or replace procedure p_test (par_a in number) is
2 begin
3 if par_a < 20 then
4 dbms_output.put_line('a is less than 20');
5 end if;
6 dbms_output.put_line('value of a is: ' || par_a);
7 end;
8 /
Procedure created.
SQL> exec p_test(15);
a is less than 20
value of a is: 15
PL/SQL procedure successfully completed.
SQL> exec p_test(34);
value of a is: 34
PL/SQL procedure successfully completed.
SQL>

DELETE Collection Method in oracle work wrong

I want to use DELETE Collection Method to delete some elements in collection
such as:
create or replace procedure testloop3 (clearaaa out nestedtable) as
type nestedtable is table of varchar2(255);
reply_ref_messageIds nestedtable;
getDelete_messageIds nestedtable;
begin
select distinct r.messagebox_id bulk collect into reply_ref_messageIds from reply r;
select m.id bulk collect into getDelete_messageIds from messagebox m;
getDelete_messageIds.delete(2);
getDelete_messageIds.delete(4);
getDelete_messageIds.delete(7);
getDelete_messageIds.delete(11);
getDelete_messageIds.delete(13);
for i in getDelete_messageIds.FIRST .. getDelete_messageIds.LAST loop
dbms_output.put_line(i);
end loop;
for i in 5 .. 12 loop
dbms_output.put_line(i);
end loop;
end;
and then I debug this procedure with plsql dev
-- Created on 2013/4/4 by THINKPAD
declare
-- Local variables here
aa nestedtable;
begin
-- Test statements here
testloop3(aa);
end;
and I get the indexes of getDelete_messageIds before remove which are 1 to 15.
However: when I debug to getDelete_messageIds.delete(2); it removes index 1 and 2...I can't explain why.
And then when I debug next statement getDelete_messageIds.delete(4); it removes index 3 and 4. And then getDelete_messageIds.delete(7); only removes index 7...
I can't understand...
your procedure, as posted, is showing nothing of the sort. you are simply looping around like
for idx in 1..15 loop
(.FIRST will resolve to 1 and .LAST will resolve to 15). it does not mean there are still 15 elements in the NT.
you are not checking to see if the indexes are deleted. I think you're confused about the proper way to loop through a nested table where there are gaps.
i.e. you can see the elements are deleted:
SQL> create table messagebox(id ) as select to_char(rownum) from dual connect by level <= 15;
Table created.
SQL> create or replace procedure testloop3
2 as
3 type nestedtable is table of varchar2(255);
4 getDelete_messageIds nestedtable;
5 v_idx number;
6 begin
7 select m.id bulk collect into getDelete_messageIds from messagebox m;
8 getDelete_messageIds.delete(2);
9 getDelete_messageIds.delete(4);
10 getDelete_messageIds.delete(7);
11 getDelete_messageIds.delete(11);
12 getDelete_messageIds.delete(13);
13 v_idx := getDelete_messageIds.first;
14 while v_idx is not null
15 loop
16 dbms_output.put_line(v_idx);
17 v_idx := getDelete_messageIds.next(v_idx);
18 end loop;
19 end;
20 /
Procedure created.
SQL> exec testloop3
1
3
5
6
8
9
10
12
14
15
so 2, 4, 7, 11, 13 are deleted. exactly as expected.
you can see
DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,3,5,7);
counter INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE('N''s first subscript is ' || n.FIRST);
DBMS_OUTPUT.PUT_LINE('N''s last subscript is ' || n.LAST);
-- When the subscripts are consecutive starting at 1,
-- it's simple to loop through them.
FOR i IN n.FIRST .. n.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Element #' || i || ' = ' || n(i));
END LOOP;
n.DELETE(2); -- Delete second element.
-- When the subscripts have gaps
-- or the collection might be uninitialized,
-- the loop logic is more extensive.
-- Start at the first element
-- and look for the next element until there are no more.
IF n IS NOT NULL THEN
counter := n.FIRST;
WHILE counter IS NOT NULL
LOOP
DBMS_OUTPUT.PUT_LINE
('Element #' || counter || ' = ' || n(counter));
counter := n.NEXT(counter);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('N is null, nothing to do.');
END IF;
END;
reference Finding the First or Last Collection Element

Resources