Oracle PLSQL BULK Collect and For Loop - oracle

I have written the following oracle procedure to fetch data in bulk and process it in blocks. I am using the bulk collect option with limit to fetch the data. But inside for loop i am not able to retrieve the ORD_ID. I am trying to output the ORD_ID using
DBMS_OUTPUT.put_line(l_orders(indx));
But getting compilation error "wrong number or type of arguments in call to 'PUT_LINE'"
create or replace PROCEDURE TESTPROC AS
CURSOR order_id_cur IS SELECT ORD_ID FROM orders ORDER BY ORD_ID ASC;
l_order_id VARCHAR2(100);
TYPE orders_aat IS TABLE OF order_id_cur%ROWTYPE;
l_orders orders_aat;
limit_in NUMBER :=10;
batch_in NUMBER :=0;
BEGIN
OPEN order_id_cur;
LOOP
FETCH order_id_cur
BULK COLLECT INTO l_orders LIMIT limit_in;
DBMS_OUTPUT.put_line('Batch-----'||batch_in);
FOR indx IN 1 .. l_orders.COUNT
LOOP
DBMS_OUTPUT.put_line(indx);
DBMS_OUTPUT.put_line(l_orders(indx));
END LOOP;
EXIT WHEN l_orders.COUNT < limit_in;
batch_in := batch_in+1;
END LOOP;
CLOSE order_id_cur;
END TESTPROC;
How can i get the values of ORD_ID inside the for loop.

Do it like this -
DBMS_OUTPUT.put_line(l_orders(indx).ORD_ID);
For example,
SQL> DECLARE
2 type t
3 IS
4 TABLE OF emp%rowtype;
5 a t;
6 BEGIN
7 SELECT * BULK COLLECT INTO a FROM emp;
8 FOR i IN 1..a.count
9 LOOP
10 dbms_output.put_line (a(i).ename);
11 END LOOP;
12 END;
13 /
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
PL/SQL procedure successfully completed.
SQL>

you can also make loop directly to cursor like below
FOR recc in order_id_cur
LOOP
DBMS_OUTPUT.put_line(recc.ORD_ID );
END LOOP;

Related

How to return more than one select queries in same procedure

I would like to ask how can i print output in procedure more than one statement.
Assume that you want to show dba_objects and segments row count. But i can not use dbms_sql.return_result my version is 11g.
Something like,
create or replace procedure get_rows_count
(
cursor1 out SYS_REFCURSOR,
cursor2 out SYS_REFCURSOR
)
as
begin
open cursor1 for select count(*) from dba_objects;
open cursor2 for select count(*) from dba_segments;
end get_rows_count;
/
Assume that you want to show dba_objects and segments row count
I assumed it. Conclusion: that's not the way to do it. If you want to get row count from e.g. dba_objects, then you should just
select count(*) from dba_objects;
in any variation you want (pure SQL, function that returns that number, procedure with an OUT parameter (worse option), ...). But, creating a procedure which uses ref cursor for that purpose is ... well, wrong.
If I got you wrong, then: procedure you wrote is OK. You can call it from another PL/SQL procedure (named or anonymous), fetch result into a variable and do something with it (e.g. display it).
Your procedure (selects from Scott's tables; I don't have access to DBA_ views):
SQL> CREATE OR REPLACE PROCEDURE get_rows_count (cursor1 OUT SYS_REFCURSOR,
2 cursor2 OUT SYS_REFCURSOR)
3 AS
4 BEGIN
5 OPEN cursor1 FOR SELECT * FROM emp;
6
7 OPEN cursor2 FOR SELECT * FROM dept;
8 END get_rows_count;
9 /
Procedure created.
How to call it? See line #8:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 rc1 SYS_REFCURSOR;
3 rc2 SYS_REFCURSOR;
4 --
5 rw1 emp%ROWTYPE;
6 rw2 dept%ROWTYPE;
7 BEGIN
8 get_rows_count (rc1, rc2);
9
10 DBMS_OUTPUT.put_line ('Employees -----------');
11
12 LOOP
13 FETCH rc1 INTO rw1;
14
15 EXIT WHEN rc1%NOTFOUND;
16
17 DBMS_OUTPUT.put_line (rw1.ename);
18 END LOOP;
19
20 --
21 DBMS_OUTPUT.put_line ('Departments ---------');
22
23 LOOP
24 FETCH rc2 INTO rw2;
25
26 EXIT WHEN rc2%NOTFOUND;
27
28 DBMS_OUTPUT.put_line (rw2.dname);
29 END LOOP;
30
31 DBMS_OUTPUT.put_line ('First ref cursor: ' || rc1%ROWCOUNT);
32 DBMS_OUTPUT.put_line ('Second ref cursor: ' || rc2%ROWCOUNT);
33 END;
34 /
Result:
Employees -----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
Departments ---------
ACCOUNTING
RESEARCH
SALES
OPERATIONS
First ref cursor: 14
Second ref cursor: 4
PL/SQL procedure successfully completed.
SQL>
You can use famous DBMS_OUTPUT.PUT_LINE() along with %ROWCOUNT suffix for your case such as
SET serveroutput ON
CREATE OR REPLACE PROCEDURE get_rows_count(
cursor1 OUT SYS_REFCURSOR,
cursor2 OUT SYS_REFCURSOR,
count1 OUT INT,
count2 OUT INT
) AS
cur_rec_obj user_objects%ROWTYPE;
cur_rec_seg user_segments%ROWTYPE;
BEGIN
OPEN cursor1 FOR SELECT * FROM user_objects;
LOOP
FETCH cursor1 INTO cur_rec_obj;
EXIT WHEN cursor1%NOTFOUND;
END LOOP;
OPEN cursor2 FOR SELECT * FROM user_segments;
LOOP
FETCH cursor2 INTO cur_rec_seg;
EXIT WHEN cursor2%NOTFOUND;
END LOOP;
count1 := cursor1%ROWCOUNT;
count2 := cursor2%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(count1);
DBMS_OUTPUT.PUT_LINE(count2);
END;
/
and you can call as follows from the SQL Window of PL/SQL Developer :
DECLARE
v_cursor1 SYS_REFCURSOR;
v_cursor2 SYS_REFCURSOR;
v_count1 INT;
v_count2 INT;
BEGIN
get_rows_count(v_cursor1, v_cursor2, v_count1, v_count2 );
END;
/

Cursor FOR LOOP with cursor as parameter

I have the following procedure executing some business logic (looping through a cursor):
PROCEDURE myproc() AS
CURSOR mycur IS
SELECT * FROM mytable;
BEGIN
FOR c IN mycur LOOP
...business logic here...
...many lines of code...
END LOOP;
END myproc;
I'd like to have different procedures and execute the same business logic for different cursors (sets of data). For example I'd like to have one procedure myproc_adopters() for:
SELECT * FROM mytable WHERE cutomer_type='Adopters'
and another procedure myproc_others() for others:
SELECT * FROM mytable WHERE customer_type!='Adopters'
So I'd like to have one main procedure mainproc() containing cursor loop and business logic and other procedures calling this main procedure and sending different cursors as parameters. The problem is that it seems that cursor FOR loop does not accept cursor as variable that I can send as procedure call parameter:
PROCEDURE myproc_adopters() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type='Adopters';
BEGIN
mainproc(mycur);
END myproc_adopters;
PROCEDURE myproc_others() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type!='Adopters';
BEGIN
mainproc(mycur);
END myproc_others;
PROCEDURE mainproc(mycur IN SYS_REFCURSOR) AS
BEGIN
FOR c IN mycur LOOP <-- does not accept cursor as variable
...
END LOOP;
END mainproc;
How to send different cursor to the same cursor FOR LOOP?
Your idea is OK (at least, to me), but - you then have to pass refcursor (as you declared it).
For example:
mainproc:
SQL> create or replace procedure mainproc (mycur in sys_refcursor)
2 is
3 l_row emp%rowtype;
4 begin
5 loop
6 fetch mycur into l_row;
7 exit when mycur%notfound;
8
9 dbms_output.put_line(l_row.ename ||' - '|| l_row.job ||' - '|| l_row.sal);
10 end loop;
11 end;
12 /
Procedure created.
This procedure accepts a parameter and - depending on it - opens refcursor with some criteria and then calls mainproc, passing that refcursor:
SQL> create or replace procedure myproc_adopters (par_deptno in emp.deptno%type) is
2 l_rc sys_refcursor;
3 begin
4 if par_deptno = 10 then
5 open l_rc for select * from emp where job = 'CLERK';
6 elsif par_deptno = 20 then
7 open l_rc for select * from emp where sal > 2000;
8 end if;
9
10 mainproc (l_rc);
11 close l_rc;
12 end;
13 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec myproc_adopters(10);
SMITH - CLERK - 840
ADAMS - CLERK - 1100
JAMES - CLERK - 950
MILLER - CLERK - 1300
PL/SQL procedure successfully completed.
SQL>
SQL> exec myproc_adopters(20);
JONES - MANAGER - 2975
BLAKE - MANAGER - 2850
CLARK - MANAGER - 2450
SCOTT - ANALYST - 3000
KING - PRESIDENT - 5000
FORD - ANALYST - 3000
PL/SQL procedure successfully completed.
SQL>
There are two other ways you could do this that don't require a refcursor:
Add a parameter to your myproc procedure, and update the cursor to retrieve the rows based on the parameter, e.g.:
PROCEDURE myproc(p_query_type IN VARCHAR2) AS
CURSOR mycur IS
SELECT *
FROM mytable
WHERE (p_query_type IS NULL -- or whatever value you want to indicate all rows, e.g. p_query_type = 'ALL'
OR (UPPER(p_query_type) = 'ADOPTERS' AND customer_type = 'Adopters')
OR (UPPER(p_query_type) = 'OTHERS' AND customer_type != 'Adopters'));
BEGIN
FOR c IN mycur LOOP
...business logic here...
...many lines of code...
END LOOP;
END myproc;
Or you could extract the business logic into its own procedure (complete with the requisite number of parameters), and the wrapper procedures call that within the cursor-for-loop, e.g.:
PROCEDURE mainproc (p_param1 mytable.col1%TYPE,
p_param2 mytable.col2%TYPE,
....)
BEGIN
...business logic here...
...many lines of code...
END mainproc;
PROCEDURE myproc_adopters() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type='Adopters';
BEGIN
FOR c IN mycur LOOP
mainproc (p_param1 => c.col1,
p_param2 => c.col2,
...)
END LOOP;
END myproc_adopters;
PROCEDURE myproc_others() AS
CURSOR mycur IS
SELECT * FROM mytable WHERE customer_type!='Adopters';
BEGIN
FOR c IN mycur LOOP
mainproc (p_param1 => c.col1,
p_param2 => c.col2,
...)
END LOOP;
END myproc_others;
I hope that these procedures are in a package! If you're going to go down the route of having wrapper procedures but your current procedure isn't in a package, I highly recommend you move it into a package.
My personal preference would be to go with a combination of both options; have the cursor decide what rows to select based on the input parameter, but also encapsulate the business logic in its own procedure, or better yet, break it down into separate procedures to make it more manageable, as one long, spaghetti-like procedure isn't good programming practice (IMO).

Stored Procedure - not able to display no data found message

I have created a procedure, which will take the Mgr number as input for execution.
When ever data is there for input passed Mgr number we are getting data. But when we don't have data it is not displaying "no data found" message.
create or replace procedure sp1 (mg number)
as
cursor c1 is select * from emp1 where mgr = mg;
i emp1%rowtype;
begin
for i in c1
loop
dbms_output.put_line(i.ename||' '||i.sal);
end loop;
exception
when no_data_found then
dbms_output.put_line('no data found');
end;
/
Can anyone please tell me how to display "no data found" message when there is no data for input Mgr number.
Thanks,
Subash
Cursor can't return no_data_found, pure select can.
Here's one workaround: declare a local counter variable and see whether it changed.
SQL> create or replace procedure sp1 (mg number) as
2 cursor c1 is select * from emp where mgr = mg;
3 i emp%rowtype;
4 l_cnt number := 0; --> this
5 begin
6 for i in c1 loop
7 l_cnt := l_cnt + 1; --> this
8 dbms_output.put_line(i.ename ||' '|| i.sal);
9 end loop;
10 if l_cnt = 0 then --> this
11 dbms_output.put_line('no data found');
12 end if;
13 end;
14 /
Procedure created.
Testing:
SQL> exec sp1(7698);
ALLEN 1600
WARD 1250
MARTIN 1250
TURNER 1500
JAMES 950
PL/SQL procedure successfully completed.
SQL> exec sp1(76982);
no data found
PL/SQL procedure successfully completed.
SQL>
If you want to do everything manually, then you have to declare cursor, cursor variable, open cursor, open loop, fetch, exit, close loop, close cursor:
SQL> create or replace procedure sp1 (mg number) as
2 cursor c1 is select * from emp where mgr = mg;
3 i emp%rowtype;
4 begin
5 open c1;
6 loop
7 fetch c1 into i;
8 exit when c1%notfound;
9 dbms_output.put_line(i.ename ||' '|| i.sal);
10 end loop;
11 close c1;
12 end;
13 /
Procedure created.
SQL> exec sp1(7698);
ALLEN 1600
WARD 1250
MARTIN 1250
TURNER 1500
JAMES 950
PL/SQL procedure successfully completed.
SQL>
By far the simplest option is a cursor for loop as Oracle does mostly everything for you:
create or replace procedure sp1 (mg number) as
begin
for c1 in (select * from emp where mgr = mg) loop
dbms_output.put_line(c1.ename ||' '|| c1.sal);
end loop;
end;
/
exec sp1(7698);

Oracle Dynamically call cursor

I've two cursors both have almost similar code just a little difference in the group by condition, I want if the id is 1 or 2 e.t.c then it should open cur1 else cur2, basically one cursor at a time. Is there any possibility of achieving this?
if id in (1,2,3,4) then
cursor_value:= 'cur1';
else
cursor_value := 'cur2';
end if;
for i in cursor_value loop
end loop;
You can use an OPEN-FOR statement. Example:
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/
One option would be to include both cursor's SELECT statements into cursor FOR loop, along with a condition that chooses which one of them will be used. For example:
SQL> declare
2 id number := &par_id;
3 begin
4 for cur_r in (select * from emp
5 where deptno = 10
6 and id in (1,2,3,4)
7 union all
8 select * from emp
9 where deptno = 20
10 and id not in (1,2,3,4)
11 )
12 loop
13 dbms_output.put_Line(cur_r.deptno ||' '||cur_r.ename);
14 end loop;
15 end;
16 /
Enter value for par_id: 1 --> ID = 1, so use SELECT for DEPTNO = 10
old 2: id number := &par_id;
new 2: id number := 1;
10 CLARK
10 KING
10 MILLER
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_id: 823 --> ID <> 1, so use SELECT for DEPTNO = 20
old 2: id number := &par_id;
new 2: id number := 823;
20 SMITH
20 JONES
20 SCOTT
20 ADAMS
20 FORD
PL/SQL procedure successfully completed.
SQL>
The simple way could be to use if else condition.
DECLARE
type cur REF CURSOR;
c cur;
BEGIN
IF id in (1,2,3,4) THEN
OPEN c FOR 'cursor query 1';
ELSE
OPEN c FOR 'cursor query 2';
END IF ;
END;
Cheers!!

PLSQL select query in BEGIN block

CREATE OR REPLACE PROCEDURE validate_date
AS
strABC DATE;
strDummy VARCHAR2(20);
CURSOR C_Example IS SELECT * from dummytable;
BEGIN
FOR R_Example IN C_RetNxWeek LOOP
strABC := R_RetNxWeek.something;
strDummy := R_RetNxWeek.something;
END LOOP;
IF (something < something) THEN
ELSE
strDummy:= SELECT * FROM sometable WHERE something = something; <-----alternative to do this?
END IF;
END validate_date;
I have a very basic store procedure template like above, i have a cursor that will select some record from a table, in the IF ELSE statement in BEGIN block, i wish to do a checking on a data by selecting a table, since i cannot put cursor in BEGIN block, how can i do that?
The select statement result cannot be stored as you stated,
it should be done via
It should use INTO Clause please refer https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/returninginto_clause.htm
No need of an explicit cursor, use a CURSOR FOR LOOP. I don't know your requirement, since I think it could be done using plain SQL. However, if you really want to use PL/SQL, you can make use of BULK COLLECT and FORALL.
And regarding the SELECT, you need to use INTO clause in PL/SQL.
SQL> DECLARE
2 eno NUMBER;
3 STR_DUMMY VARCHAR2(20);
4 BEGIN
5 FOR i IN
6 (SELECT * FROM emp
7 )
8 LOOP
9 eno := i.empno;
10 dbms_output.put_line(eno);
11 END LOOP;
12 SELECT ENAME INTO STR_DUMMY FROM EMP WHERE EMPNO = 7788;
13 dbms_output.put_line(STR_DUMMY);
14 END;
15 /
7369
7499
7521
7566
7654
7698
7782
7788
7839
7844
7876
7900
7902
7934
SCOTT
PL/SQL procedure successfully completed.
SQL>

Resources