Is there a way to close the cursor after reading it? - oracle

working with oracle sql and oracle service bus.
I have a problem with closing the connection.
An example of a simple procedure I use is the following :
create or replace procedure getempdata(
v_id in number,
q out sys_refcursor
)
as
begin
open q for select * from employees where id = v_id;
end;
I need a way to close the cursor after getting the data from it.
but if I use close Q; , I can't read the data returned in service bus adapter .
The question is :
Is there a way to close the cursor (for memory management) from service bus after reading it?
And if there is not, Is there a way to do so from the procedure with returning the data as output?
Note: I don't call the procedure from sql code, so I can close it. I call it as a webservice using service bus webservice that calls the procedure by a database adapter; so I need the data returned in service bus

You'd close it from the caller.
Your procedure (adjusted to Scott's sample schema):
SQL> create or replace procedure getempdata(
2 v_id in number,
3 q out sys_refcursor
4 )
5 as
6 begin
7 open q for select ename from emp where deptno = v_id;
8 end;
9 /
Procedure created.
Let's call it:
SQL> set serveroutput on
SQL> declare
2 l_rc sys_refcursor;
3 l_ename emp.ename%type;
4 begin
5 getempdata(10, l_rc); --> procedure is called here
6 loop
7 fetch l_rc into l_ename;
8 exit when l_rc%notfound;
9 dbms_output.put_line(l_ename);
10 end loop;
11 close l_rc; --> cursor is closed here
12 end;
13 /
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL>

Yes, close it once you have read from it (or if an exception occurs that prevents reading from it) using the CLOSE statement.
DECLARE
v_cur SYS_REFCURSOR;
v_row EMPLOYEES%ROWTYPE;
BEGIN
-- call the procedure
getempdata(1, v_cur);
-- read the rows
LOOP
FETCH v_cur INTO v_row;
EXIT WHEN v_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_row.id);
END LOOP;
-- close the cursor
CLOSE v_cur;
EXCEPTION
WHEN OTHERS THEN -- Handling OTHERS is bad practice, normally you should be more specific
IF v_cur%ISOPEN THEN
CLOSE v_cur;
END IF;
END;
/
fiddle

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

PL/SQL Procedure SELECT Input- Error PLS-00306

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.

Pass PL/SQL parameter as SCHEMA NAME

I'm trying to send variable schema name to cursor via procedure input
Here is my lame try, but you can see what I want to do:
CREATE OR REPLACE PROCEDURE HOUSEKEEPING
(SCHEMANAME in varchar2)
IS
CURSOR data_instances IS select table_name
from SCHEMANAME.table_name where TYPE='PERMANENT' and rownum<200 ;
BEGIN
DBMS_OUTPUT.PUT_LINE(SCHEMANAME);
END;
/
it throws expected
PL/SQL: ORA-00942: table or view does not exist
is there lawful way to make schema name work as variable? thanks
There is a way; you'll need some kind of dynamic SQL because you can't use schema (or object) names like that. For example, you could use refcursor instead.
Sample table:
SQL> create table table_name as
2 select 'EMP' table_name, 'PERMANENT' type from dual union all
3 select 'DEPT' , 'TEMPORARY' from dual union all
4 select 'BONUS' , 'PERMANENT' from dual;
Table created.
Procedure; note the way I composed SELECT statement first (so that I could display it and check whether it is correct), and then used it in OPEN. Loop is here to ... well, loop through the cursor. I'm just displaying table names I found - you'd probably do something smarter.
SQL> create or replace procedure housekeeping (par_schemaname in varchar2)
2 is
3 l_str varchar2(500);
4 l_rc sys_refcursor;
5 l_table_name varchar2(30);
6 begin
7 l_str := 'select table_name from ' ||
8 dbms_assert.schema_name(upper(par_schemaname)) ||
9 '.table_name where type = ''PERMANENT'' and rownum < 200';
10 open l_rc for l_str;
11
12 loop
13 fetch l_rc into l_table_name;
14 exit when l_rc%notfound;
15
16 dbms_output.put_line(l_table_name);
17 end loop;
18 close l_rc;
19 end;
20 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec housekeeping('SCOTT');
EMP
BONUS
PL/SQL procedure successfully completed.
SQL>

Get count value by using cursor

DECLARE
l_rcursor SYS_REFCURSOR;
BEGIN
OPEN l_rcursor FOR SELECT * FROM all_users;
dbms_output.put_line(l_rcursor%ROWCOUNT);
END;
This is long code so i cant use the cursor inside the declare portion. Here i need the count of rows fetched. I cant use rowtype because it is a join query.
Well, you can not.
There's a way to find out whether cursor contains anything (using the %NOTFOUND attribute), such as
SQL> declare
2 l_rcursor sys_refcursor;
3 l_rec all_users%rowtype;
4 begin
5 open l_rcursor for select * From all_users
6 where 1 = 2; --> will cause nothing to be returned
7
8 -- check whether there is (or is not) anything there
9 fetch l_rcursor into l_rec;
10 if l_rcursor%notfound then
11 dbms_output.put_line('There''s nothing there');
12 end if;
13 end;
14 /
There's nothing there
PL/SQL procedure successfully completed.
SQL>
but you can't know how many rows it'll return, which means that you'll have to count number of rows elsewhere (probably code that uses what you wrote in your question).
Possible duplicate to counting rows from a cursor in pl/sql
You can also just use COUNT in your cursor query to solve your problem, sample code below
DECLARE
l_rcursor SYS_REFCURSOR;
v_count NUMBER;
BEGIN
OPEN l_rcursor FOR SELECT COUNT(1) FROM (SELECT * FROM all_users);
-- OR
-- OPEN l_rcursor FOR SELECT COUNT(1) FROM all_users;
FETCH l_rcursor INTO v_count;
dbms_output.put_line(v_count);
END;
/

Resources