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).
Related
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;
/
Hello I'm a beginner at PL/SQL and some help would be appreciated.
So I have this procedure here and my goal is to have it so that when this procedure is executed that I can enter a 5 digit integer (a zipcode) and it will just select those values from the table and display just as if I've done a query like
SELECT * FROM customers WHERE customer_zipcode = "input zipcode".
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR)
-- User input Variable, Display Variable
IS
BEGIN
OPEN p_disp for SELECT customer_first_name, customer_zipcode FROM customers
WHERE customer_zipcode=p_zipcode;
EXCEPTION
-- Input Sanitization
WHEN no_data_found THEN
dbms_output.put_line('-1');
END;
EXEC LIST_CUSTOMER_ZIPCODE(07080);
When I execute this command I just keep getting this error.
https://i.stack.imgur.com/nCI8T.png
If you are using SQL*Plus or SQL Developer then you can declare a bind variable and then call the procedure passing the variable and then print it:
SELECT * FROM customers WHERE customer_zipcode = "input zipcode".
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_disp FOR
SELECT customer_first_name, customer_zipcode
FROM customers
WHERE customer_zipcode = p_zipcode;
EXCEPTION
-- Input Sanitization
WHEN no_data_found THEN
dbms_output.put_line('-1');
END;
/
VARIABLE cur SYS_REFCURSOR;
EXEC LIST_CUSTOMER_ZIPCODE('07080', :cur);
PRINT cur;
However, your exception handling block is never going to be called as the cursor can return zero rows without raising that exception so the procedure could be simplified to:
create or replace PROCEDURE LIST_CUSTOMER_ZIPCODE(
p_zipcode IN customers.customer_zipcode%TYPE,
p_disp OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_disp FOR
SELECT customer_first_name, customer_zipcode
FROM customers
WHERE customer_zipcode = p_zipcode;
END;
/
You can't just execute such a procedure as it expects 2 parameters; one is IN, while another is OUT (ref cursor which contains result set).
I don't have your tables so I'll demonstrate it using Scott's sample schema by passing department number and returning list of employees working in that department.
Procedure:
SQL> set serveroutput on
SQL> create or replace procedure p_list
2 (p_deptno in dept.deptno%type,
3 p_disp out sys_refcursor
4 )
5 is
6 begin
7 open p_disp for select ename, job from emp
8 where deptno = p_deptno;
9 end;
10 /
Procedure created.
This is how you use it:
SQL> declare
2 l_list sys_refcursor;
3 l_ename emp.ename%type;
4 l_job emp.job%type;
5 begin
6 p_list(10, l_list); --> calling the procedure; use 2 parameters
7
8 loop
9 fetch l_list into l_ename, l_job;
10 exit when l_list%notfound;
11 dbms_output.put_line(l_ename ||' - '|| l_job);
12 end loop;
13 end;
14 /
CLARK - MANAGER
KING - PRESIDENT
MILLER - CLERK
PL/SQL procedure successfully completed.
SQL>
TRY EXEC LIST_CUSTOMER_ZIPCODE(:p_Zipcode);
If you put ':' before any string, It will become substitution string and you can type your input.
I have a cursor inside package specification like below
Create package pkgname
is
Cursor C1(report_date date) is select * from some_table where some_date=report_date;
procedure proc(repdate date)
end;
I want a functionality that assign this cursor C1 to other cursor in procedure of the same package body.
Create package body pkgname is
Procedure proc(repdate date)
Is
Cursor C2 is C1(repdate);
Begin
for j in C2 loop
--do some task;
end loop;
End;
End;
Why would you do that? If you declared the cursor in package specification, simply use it in package body. For example:
SQL> create or replace package pkg_test as
2 cursor c1 (par_job varchar2) is
3 select empno, ename from emp
4 where job = par_job;
5 procedure proc (par_job in varchar2);
6 end;
7 /
Package created.
SQL> create or replace package body pkg_test as
2 procedure proc (par_job in varchar2) is
3 begin
4 for cur_r in pkg_test.c1(par_job) loop --> here it is!
5 dbms_output.put_line(cur_r.empno ||' '|| cur_r.ename);
6 end loop;
7 end;
8 end;
9 /
Package body created.
SQL> set serveroutput on
SQL> exec pkg_test.proc('CLERK');
7369 SMITH
7876 ADAMS
7900 JAMES
PL/SQL procedure successfully completed.
SQL>
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);
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;