Skip to Main Content
  • Questions
  • Problem getting sys_refcursor to be accepted in a table function

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Robin.

Asked: May 28, 2005 - 4:29 pm UTC

Last updated: November 29, 2007 - 7:20 pm UTC

Version: 9.2.0

Viewed 1000+ times

You Asked


Problem I am trying to solve:
I need to read and process data from several tables. Each has the identical structure but contain different data.

I wrote a pipelined table function that worked against a view. I then reset the view to whatever table I needed to see. This was just done as a proof of concept. (key excerpts are shown below).

However, I do not want to continually update my view when I need to run the table function against a new table. So, I tried to replace the code using a sys_refcursor and a cursor variable.

Most of the things I've tried ended in compilation errors, but the current example (shown below) compiles. However, when I run it, I get
ORA-22905: cannot access rows from a non-nested table item

Clearly, I am not getting some key conceot, and any insights or suggestions you may have would be appreciated!

**********************
Here are the critical portions from the code that did work:

-- there are actually 100+ variables in the object, but 3 should illustrate the concept just fine.
create or replace
TYPE statsData_t as object (
sample_time NUMBER(11) ,
element_id NUMBER(11) ,
var108 NUMBER(38,7)
);
/


create or replace
TYPE StatsDataRecSet_t IS TABLE OF StatsData_t;
/
CREATE or replace
PACKAGE refcur_pkg IS
TYPE refcur_t IS REF CURSOR;
END refcur_pkg;
/


Create or replace package
regdata_pkg as


FUNCTION regularize (c1 IN refcur_pkg.refcur_t) return StatsDataRecSet_t pipelined;
procedure nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER);
end regdata_pkg;

/


create or replace package body
regdata_pkg is



CREATE or replace
FUNCTION regularize (c1 refcur_pkg.refcur_t)
RETURN StatsDataRecSet_t pipelined
PARALLEL_ENABLE(PARTITION c1 BY ANY)
IS


in_rec nhv_stats0%rowtype;
out_rec statsData_t := statsData_t(
NULL,
NULL,
NULL
);

begin


LOOP
FETCH c1 INTO in_rec;
EXIT WHEN c1%NOTFOUND;


out_rec.sample_time := in_rec.sample_time;
out_rec.element_id := in_rec.Element_id;
out_rec.VAR108 := in_rec.VAR108;
pipe row (out_rec);

END LOOP; -- the cursor loop

RETURN;
end;


procedure nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER)
IS

start_date NUMBER;
end_date NUMBER;
num_loops NUMBER;
sqlCommand VARCHAR2(1000);
BEGIN


sqlCommand := 'create or replace view nhv_stats0 as select s.* from ' || statsView || ' s where var81 > ' || startUTC || ' and var81 <= ' || endUTC;

execute immediate sqlCommand;

insert /* append */ into nh_reg0
select sample_time, element_id,
VAR108

from table(regularize(CURSOR(select * from nhv_stats0)));


commit;

END;
--/

************Now here is the latest variant of my trying to do it with the view removed. It compiles but fails when run:



create or replace
TYPE statsData_t as object (
sample_time NUMBER(11) ,
element_id NUMBER(11) ,
var108 NUMBER(38,7)
);
/



Create or replace package
regdata_pkg as


TYPE StatsDataRecSet_t IS TABLE OF StatsData_t;
FUNCTION regularize (c1 IN sys_refcursor) return StatsDataRecSet_t pipelined;
procedure nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER)'
end regdata_pkg;

/

create or replace package body
regdata_pkg is

cursor stats_cursor is select * from nh_stats;




--=====================================================
--ROUTINE: REGULARIZE
--
-- This is the heart of the regularization process
-- Given a cursor, it will return regularized rows of data
--=================================================
FUNCTION regularize (c1 IN sys_refcursor)
RETURN StatsDataRecSet_t pipelined
--PARALLEL_ENABLE(PARTITION c1 BY ANY)
IS

in_rec stats_cursor%ROWTYPE;

out_rec statsData_t := statsData_t(
NULL, NULL, NULL );


begin


LOOP
FETCH c1 INTO in_rec;
EXIT WHEN c1%NOTFOUND;

out_rec.sample_time := in_rec.sample_time;
....

RETURN;
end regularize;


procedure nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER)
IS

start_date NUMBER;
end_date NUMBER;
num_loops NUMBER;
sqlCommand VARCHAR2(1000);

statsViewCursor sys_refcursor;

BEGIN

open statsViewCursor for 'select s.* from ' || statsView || ' s where var81 > :startUTC
and var81 <= :endUTC ' using startUTC, endUTC;

insert /* append */ into nh_reg0
select sample_time, element_id,
VAR108

from table(regularize(CURSOR(statsViewCursor)));

close statsViewCursor;

END nh_regularize;
--/


It fails on the "insert ... into nh_reg0 ... from table ..." statement with the following error:
/home/cms/configSpecs:\oerr ora 22905
22905, 00000, "cannot access rows from a non-nested table item"
// *Cause: attempt to access rows of an item whose type is not known at
// parse time or that is not of a nested table type
// *Action: use CAST to cast the item to a nested table type

I actually tried casting it to every object type that made sense (and even some that didn't), but all casts returned invalid data type.





and Tom said...

It would be like this:


execute immediate '
insert /*+ APPEND */
select sample_time, element_id, var108
from TABLE( regularize( cursor( ' || l_query || ' ) ) )'


(and regularize is the guy that needs to close the cursor!!! )

o append hint needs /*+
o the cursor function takes a SQL query, ref cursors (like booleans) are known ONLY to plsql -- sql cannot "deal" with them.


So, instead of dynamically opening a ref cursor, you dynamically execute the insert with the query in it.


Now, since this uses append, I'm assuming this is a batch -- executed once in a while, not every minute, hour or even day. So, bind variables are something you could skip if true (in fact, might want to skip if the query is infrequent AND the optimization of the innermost query is paramount).

Else, you can use bind variables in the execute immedate.

Rating

  (6 ratings)

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

Comments

This doesn't seem to work

Robin Trei, June 01, 2005 - 10:21 am UTC

When I follow your suggestion: I get the following error:
ORA-00904: "TST_REGULARIZE": invalid identifier

I have created a small test program that shows my original attempt (that worked), the one that compiled and failed, and your suggestion. I'm not sure it will make the 1000 word limit:
drop table test_reg0;

drop table test_base;

create table test_reg0 ( sample_time number(11), element_id number (11) , var108 number(38,7));

create table test_base as select * from test_reg0;

create view test_view as select * from test_base;

insert into test_base (sample_time, element_id, var108) values ( 1117631002, 1000001, 2049.81006);
insert into test_base (sample_time, element_id, var108) values ( 1117631302, 1000001, 1608);
insert into test_base (sample_time, element_id, var108) values (1117631601, 1000001, 2063.1001);
insert into test_base (sample_time, element_id, var108) values ( 1117631903, 1000001, 2579.08008);
insert into test_base (sample_time, element_id, var108) values ( 1117632201, 1000001, 2410.82007);
insert into test_base (sample_time, element_id, var108) values ( 1117632501, 1000001, 2808);
/

create or replace
TYPE testData_t as object (
sample_time NUMBER(11) ,
element_id NUMBER(11) ,
var108 NUMBER(38,7)
);
/



Create or replace package
testdata_pkg as


TYPE TestDataRecSet_t IS TABLE OF TestData_t;
FUNCTION tst_regularize (c1 IN sys_refcursor) return TestDataRecSet_t pipelined;
procedure tst_nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER);
end testdata_pkg;

/

create or replace package body
testdata_pkg is

cursor stats_cursor is select * from test_base;




--=====================================================
--ROUTINE: REGULARIZE
--
-- This is the heart of the regularization process
-- Given a cursor, it will return regularized rows of data
--=================================================
FUNCTION tst_regularize (c1 IN sys_refcursor)
RETURN TestDataRecSet_t pipelined
--PARALLEL_ENABLE(PARTITION c1 BY ANY)
IS


in_rec stats_cursor%ROWTYPE;

out_rec testData_t := testData_t(
NULL, NULL, NULL );


begin


LOOP
FETCH c1 INTO in_rec;
EXIT WHEN c1%NOTFOUND;

out_rec.sample_time := in_rec.sample_time;
out_rec.element_id := in_rec.element_id;
out_rec.var108 := in_rec.var108;
pipe row(out_rec);
end loop;
RETURN;
end tst_regularize;


procedure tst_nh_regularize (statsView VARCHAR2, startUTC NUMBER, endUTC NUMBER)
IS
-- call this as tst_nh_regularize('test_base', 1117631002, 1117632501)
start_date NUMBER;
end_date NUMBER;
num_loops NUMBER;
sqlCommand VARCHAR2(1000);

statsViewCursor sys_refcursor;

BEGIN
-- if you uncomment the lines marked '*1' you get a program that works, but relies on a view being regenerated
-- If you uncomment the lines marked '*2' you get a program that runs, but produces an ora-22905 exception
-- If you uncomment the lines marked '*3*' you get the program change you suggested, which does not recognize the table function
-- it returns: ORA-00904: "TST_REGULARIZE": invalid identifier

--*1* sqlCommand := 'create or replace view test_view as select * from ' || statsView;

--*2* open statsViewCursor for 'select sample_time, element_Id, var108 from ' || statsView ;
--*3* sqlCommand := 'select sample_time, element_id, var108 from ' || statsView;
--*3* execute immediate '
insert /* append */ into test_reg0
select sample_time, element_id,
VAR108

from table(tst_regularize(
--*3* cursor(' || sqlCommand || ')
--*1* cursor(select * from test_view)
--*2* statsViewCursor
))
--*3* '
;
--*2* close statsViewCursor;

END tst_nh_regularize;

end testdata_pkg;
/


Tom Kyte
June 01, 2005 - 11:23 am UTC

so, what does it fail with (please, for a test case -- remove everything NOT RELEVANT -- like all of the commented lines, hacked code -- short, sweet, to the point)

and use dbms_output to print out your generated sql, you might well find the bug in the generated SQL rapidly that way.


but I can say:

from table(tst_regularize(
^^^^^ missing PACKAGE NAME


(an example for me should be "easy" to work with -- 1, 2, 3 isn't "easy" and I don't have the table stats_cursor so even when I go to 3, the code doesn't compile)

you are quite correct, this does work!

Robin Trei, June 01, 2005 - 11:44 am UTC

You are quite correct, this does work. The problem was that I forgot the package name and it mattered when I went to dynamic sql.
(I had just done so many versions of trying to solve this problem that I was completely misinterpreting what the error message was saying.)

Thank you very much for your time, I really appreciate the help. I do apologize that my demo script was not helpful, I can understand how truly busy you are.


Tom Kyte
June 01, 2005 - 1:49 pm UTC

I think that tomorrow I'll blog about "how to ask other people questions" (no slam on you -- really, not at all).

I do get lots of them, as do the forums in general. There are some simple things that would make it lots easier for people to answer if they had it up front :)

(ran out of time today, no blog for me -- off to the richmond Oracle user group, be on the road for 6 hours driving instead of typing)

Same things on my mind

Holger Bär, June 03, 2005 - 7:49 am UTC

<quote> I think that tomorrow I'll blog about "how to ask other people questions" (no
slam on you -- really, not at all).</quote>

You must be kidding, right? I was thinking about starting a blog myself and that would have been the very first item.

So I'll be enjoying your blog instead.

Id pleae

Srinivasan. A, September 27, 2005 - 1:28 am UTC

I have many doubts in oracle & vb. pls tell me which Id to send my question?

Thanks and regards
Srinivasan. A


Tom Kyte
September 27, 2005 - 11:00 am UTC

</code> http://www.amazon.com/exec/obidos/tg/detail/-/1590594258/qid=1127832909/sr=8-2/ref=sr_8_xs_ap_i2_xgl14/104-3959211-7604732?v=glance&s=books&n=507846 <code>


is a pretty good book - and in the tabs above, there are "other resources", many forums out there.

Dynamic pipelined functions?

J L, October 01, 2005 - 12:57 am UTC

Hi Tom,

Thanks,

I would like your help to create a function that would select certain columns of a given query (for the example it will be a table), based on columns defined in another table.

But I would like to use pipelined functions,

Here are the tables ..

create table x
( col1 varchar2(10),
col2 varchar2(10),
col3 varchar2(10),
col4 varchar2(10),
col5 varchar2(10),
col6 varchar2(10)
) ;

create table v
( v_name varchar2(20),
v_order integer ,
v_col_name varchar2(20)
) ;

insert into x values ( 'EMP','2321','PETER1','SMITH1','11-SEP-05','OFF01' ) ;
insert into x values ( 'EMP','2322','PETER2','SMITH2','12-SEP-05','OFF02' ) ;
insert into x values ( 'EMP','2323','PETER3','SMITH3','13-SEP-05','OFF03' ) ;
insert into x values ( 'EMP','2324','PETER4','SMITH4','14-SEP-05','OFF01' ) ;
insert into x values ( 'EMP','2325','PETER5','SMITH5','15-SEP-05','OFF04' ) ;
insert into x values ( 'EMP','2326','PETER6','SMITH6','16-SEP-05','OFF05' ) ;
insert into x values ( 'EMP','2327','PETER7','SMITH7','17-SEP-05','OFF06' ) ;

insert into v values ( 'VIEW01',1,'COL1' );
insert into v values ( 'VIEW01',2,'COL2' );
insert into v values ( 'VIEW01',3,'COL3' );
insert into v values ( 'VIEW01',4,'COL4' );

insert into v values ( 'VIEW02',1,'COL1' );
insert into v values ( 'VIEW02',2,'COL5' );
insert into v values ( 'VIEW02',3,'COL6' );

commit ;

the desired output should be:

select * from table ( MyFunction ( 'VIEW01' ) ) ;

COL1 COL2 COL3 COL4
---- ---- ------ ------
EMP 2321 PETER1 SMITH1
EMP 2322 PETER2 SMITH2
EMP 2323 PETER3 SMITH3
EMP 2324 PETER4 SMITH4
EMP 2325 PETER5 SMITH5
EMP 2326 PETER6 SMITH6
EMP 2327 PETER7 SMITH7

and in case the parameter is changed, should give other columns ...

select * from table ( MyFunction ( 'VIEW02' ) ) ;

COL1 COL5 COL6
---- --------- ------
EMP 11-SEP-05 OFF01
EMP 12-SEP-05 OFF02
EMP 13-SEP-05 OFF03
EMP 14-SEP-05 OFF01
EMP 15-SEP-05 OFF04
EMP 16-SEP-05 OFF05
EMP 17-SEP-05 OFF06

Actually my question is if a pipelined function can be created and defined to return a generic ref cursor ?

Your suggestions are greately appreciated,


Tom Kyte
October 01, 2005 - 9:02 pm UTC

pipelined functions do not RETURN ref cursors, they return collections!

How to return a collection with unnown number of columns

Maya, November 29, 2007 - 6:04 pm UTC

I think what author meant (and what I am trying to figure out) is how to write a pipeline function which takes a sys_refcursor as a parameter (meaning you do not know how many columns are in the cursor select list) and returns a collection (of what type?) each record of which is a fetched cursor record. In this case you can query this table function as you would query from any view.
Thanks.

Tom Kyte
November 29, 2007 - 7:20 pm UTC

you cannot do that in 10g and before - not until 11g would anything remotely similar to this be possible (and I haven't even thought about trying it there because I question "why")

you need to turn the ref cursor into a dbms_sql cursor, you can do that in 11g.

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