Skip to Main Content
  • Questions
  • Returning result with multiple records from one procedure to a call procedure in a loop

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Matthew.

Asked: December 18, 2004 - 9:47 am UTC

Last updated: June 30, 2005 - 3:55 pm UTC

Version: 9.1.2

Viewed 1000+ times

You Asked

Given:
create table a(tabname varchar(512));
insert into a values ('matt');
insert into a values ('bob');

create or replace package test as
TYPE return_cursor IS REF CURSOR;

procedure innerproc(tab_name IN VARCHAR2, innercurout OUT test.return_cursor);

procedure outerproc(outercurout OUT test.return_cursor);
end test;

create or replace procedure innerproc (tab_name IN VARCHAR2, innercurout out test.return_curor ) IS
begin
-- do something that opens innercurout as selects multiple rows based on tab_name
end innerproc;

create or replace procedure outerproc(outercurout OUT test.return_cursor);
CURSOR outer_test_cur IS
SELECT tab_name from a;
begin

for outer_test_index in outer_test_cur
loop
innercurout(outer_test_index.tab_name in, outercurout);
end loop;
end outerproc;
end test;

OK the above (other than typos) does not work but I hope will show what I am trying to do. In english I want to create a proc that opens a cursor that is used in a cursor for loop to call another proc. Get the result set from that inner proc and accumulate it with the result set from the next interation of the loop.

I had hoped that the ref cursors would act more like true pointers but I suspect I pushed the indirection somewhat. Do I need to build a pl/sql table? I tried that but could not get the data from the ref cursor of the inner proc into the table, because of the weak casting??

For a variety of reason the inner proc must/should use a ref cursor (code reuse).

Thank You
Matt

and Tom said...

ref cursors truly are pointers, and just like in C -- if you have a function that takes the address of an address and assigns a new address to it -- you'd lose what the old address points to! ref cursors are "in out" type of things. They get pointed TO a result set, it is not that a result set is copied into whereever they are pointing.

What we can do is use a piplined function -- to have the outerproc return rows from the ref cursors from inner proc as soon as they are generated. It would look something like this:



ops$tkyte@ORA9IR2> create or replace type myScalarType as object
2 ( x int, y date, z varchar2(30) )
3 /

Type created.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type myArrayType as table of myScalarType
2 /

Type created.

think of those as your "view", think of them as you would "create view", they are just definitions -- yes, you must have them


ops$tkyte@ORA9IR2> create or replace package demo_pkg
2 as
3 procedure a ( p_input in number, p_cursor in out sys_refcursor );
4 function b return myArrayType PIPELINED;
5 end;
6 /
Package created.

that'll let us select * from that plsql function..

ops$tkyte@ORA9IR2> create or replace package body demo_pkg
2 as
3 procedure a ( p_input in number, p_cursor in out sys_refcursor )
4 is
5 begin
6 open p_cursor for
7 select user_id, created, username
8 from all_users
9 where user_id = p_input;
10 end;
11
12 function b return myArrayType PIPELINED
13 is
14 l_cursor sys_refcursor;
15 l_data myScalarType := myScalarType( null, null, null );
16 begin
17 for x in ( select * from all_users where rownum < 8 )
18 loop
19 a( x.user_id, l_cursor );
20 loop
21 fetch l_cursor into l_data.x, l_data.y, l_data.z;
22 exit when l_cursor%notfound;
23 pipe row ( l_data );
24 end loop;
25 close l_cursor;
26 end loop;
27 return;
28 end;
29 end;
30 /
Package body created.

Line 23 is the magic, it sends the row back to the client -- right away (client gets the first row from this process way before we process the last row in the plsql procedure -- it "pipes" the data...)



ops$tkyte@ORA9IR2> select * from table( demo_pkg.b );

instead of calling demo_pkg.b to get a ref cursor, client opens the cursor themselves -- invoking demo_pkg.b that way...



X Y Z
---------- --------- ------------------------------
0 18-SEP-04 SYS
5 18-SEP-04 SYSTEM
11 18-SEP-04 OUTLN
19 18-SEP-04 DBSNMP
49 24-SEP-04 SCOTT
21 19-SEP-04 WMSYS
30 19-SEP-04 ORDSYS

7 rows selected.


Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Returning result with multiple rows in ORACLE 8I

Piter, December 18, 2004 - 4:25 pm UTC

Does the idea of pipeline function work also in ORACLE 8I?
WILL THE ABOVE EXMAPLE WORK IN Oracle 8i?
If not ,how is it possible to match it to Oracle 8i?
What do you mean in "instead of calling demo_pkg.b to get a ref cursor, client opens the cursor themselves"?
The query SELECT * FROM TABLE (DEMO_PKG.B) WILL BE EXECUTED IN DATABASE SERVER.SO what is the client?
The query of kind SELECT * FROM TABLE(DEMO_PKG.B) WILL WORK ALSO IN ORACLE 8I?
Thank's.
Piter

Tom Kyte
December 18, 2004 - 5:14 pm UTC

In 8i, you would have to fill a collection up (a local variable) and return the entire collection at the end (eg: client gets first row AFTER you process last row and all rows must fit into PGA memory)

by "instead of calling.... opens the cursor themsevles"

I mean, instead of the client preparing and executing:

   begin demo_pkg.b( :x ); end;

client prepares and executes

   select * from table( demo_pkg.b );

the client is whatever was getting the refcursor originally.


types are unchanged, here are the mods for 8i:

tkyte@ORA8IW> create or replace package demo_pkg
  2  as
  3      type rc is ref cursor;
  4      procedure a ( p_input in number, p_cursor in out rc );
  5      function b return myArrayType ;
  6  end;
  7  /

Package created.

tkyte@ORA8IW> create or replace package body demo_pkg
  2  as
  3      procedure a ( p_input in number, p_cursor in out rc )
  4      is
  5      begin
  6          open p_cursor for
  7          select user_id, created, username
  8            from all_users
  9           where user_id = p_input;
 10      end;
 11
 12      function b return myArrayType
 13      is
 14          l_cursor rc;
 15          l_data   myScalarType := myScalarType( null, null, null );
 16          l_array  myArrayType := myArrayType();
 17      begin
 18          for x in ( select * from all_users where rownum < 8 )
 19          loop
 20              a( x.user_id, l_cursor );
 21              loop
 22                  fetch l_cursor into l_data.x, l_data.y, l_data.z;
 23                  exit when l_cursor%notfound;
 24                  l_array.extend;
 25                  l_array(l_array.count) := l_data;
 26              end loop;
 27              close l_cursor;
 28          end loop;
 29          return l_array;
 30      end;
 31  end;
 32  /

Package body created.

tkyte@ORA8IW>
tkyte@ORA8IW> select * from table( cast( demo_pkg.b as myArrayType ) );

         X Y         Z
---------- --------- ------------------------------
         0 14-NOV-00 SYS
         5 14-NOV-00 SYSTEM
        11 14-NOV-00 OUTLN
        41 08-NOV-04 OPS$TKYTE
        25 14-NOV-00 AURORA$JIS$UTILITY$
        26 14-NOV-00 OSE$HTTP$ADMIN
        27 14-NOV-00 AURORA$ORB$UNAUTHENTICATED

7 rows selected. 

What is cast?

A reader, December 18, 2004 - 5:20 pm UTC

PITER

Tom Kyte
December 18, 2004 - 6:10 pm UTC

a function.

documented in the sql reference manual. was needed there in 8i.

Very Interesting!

A reader, December 18, 2004 - 9:24 pm UTC

Filling the collection up and returning the resultsets back to client at the end is not equivalent to piping the row , right?

Does this mean that we can not achieve piping functionality in oracle 8i?

Tom Kyte
December 19, 2004 - 10:38 am UTC

correct - i pointed out the differences (if they were the same, we would not have changed it in 9i to add pipeline)

there is no "piping" in 8i

Awesome example of Wicked Powerful feature!

Dave Sanabria, January 06, 2005 - 11:59 am UTC

One of my favorite features of Informix SPL was its ability to return data from a procedure one row at a time (piping). This makes dynamic queries hum and gives you some serious raw power for complex reporting.

I have been stuck with 8i for a long time, and although I see 9i in my future (< 6 months) I didn't even know about the pipline capability until just recently.

Can you say "Wicked Cool!"

RE : Ref Cursor

A reader, June 30, 2005 - 3:17 pm UTC

Hi Tom,

From the first discussion it looks like a REF CURSOR is a pointer to a pointer. Can u pls. clarify what the double re-direction is for.
1. My understanding is that a regular cursor is a pointer to a parsed representation of a SQL statement in library cache. ie a regular cursor is a single re-direction.
2. A ref cursor is a double re-direction.

Could you please confirm my understanding on 1 and 2. If my understanding on ref cursor is correct, can you pls explain why it is a double re-direction and what each redirection is for.
ie. a normal cursor is like
points
to
Ordinary Cursor ----> a parsed representation of SQL

Ref Cursor ------> Address 1 -------> parsed SQL stmt

What is that double redirection in case of ref cursor and how and where setting session_cached_cursor comes into play?

Your help is highly appreciated as I have C programming background and would be helpful if you can explain in terms of pointers and pointers to pointers etc



Tom Kyte
June 30, 2005 - 3:55 pm UTC

a ref cursor is simply a variable that can hold a cursor. that is all, don't over analyze it. it is just a cursor.

I wouldn't even really call a cursor a pointer in the tradition of C actually. A cursor is more like a FILE in C.

a ref cursor is simply more "flexible" than a (static) cursor.

don't even try to lay C on top of a Cursor. It is more like a "FILE *" if you want to think of it in C terms (and hence a FILE **)

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library