Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Matt.

Asked: August 11, 2003 - 4:03 pm UTC

Last updated: July 19, 2013 - 1:00 pm UTC

Version: 9.0.1

Viewed 10K+ times! This question is

You Asked

Tom,

I have a cursor defined statically currently which I now need to make dynamic based on the fact that the table name will change. I would like to know the recommended approach for this type of scenario.

My code essentially will behave like the sample below but I will need to be able to switch out "all_tables".

DECLARE

CURSOR inrec_cursor IS
SELECT * from all_tables
where rownum < 10;
BEGIN

FOR inrec IN inrec_cursor LOOP

DBMS_OUTPUT.PUT_LINE('Tablename = ' || inrec.table_name);

END LOOP;

END;
/

UPDATED INFO: Yes all of the tables have the exact same structure. We are running a data transformation process with multiple streams. The number of streams can be dynamic so at run time we look at a list of available tables (i.e. process_1, process_2, etc.) to determine which table to use. We are now implementing this dynamic table lookup which explains the need to be able to replace the table name.

I apologize if you have answered this question previously. I was unable to find the answer.

Thanks,

Matt

and Tom said...

in 9iR1, this would look like this:


ops$tkyte@ORA9I> create table t1 as select owner, object_name, object_id, created from all_objects where rownum < 500;

Table created.

ops$tkyte@ORA9I> create table t2 as select owner, object_name, object_id, created from all_objects where rownum < 1000;

Table created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I>
ops$tkyte@ORA9I> create or replace procedure process( p_tname in varchar2, p_array_size in number default 100 )
2 as
3 l_cursor sys_refcursor;
4 l_owner dbms_sql.varchar2_table;
5 l_object_name dbms_sql.varchar2_table;
6 l_object_id dbms_sql.number_table;
7 l_created dbms_sql.date_table;
8 l_processed_cnt number := 0;
9 begin
10 open l_cursor for 'select * from ' || p_tname;
11 loop
12 fetch l_cursor BULK COLLECT
13 into l_owner, l_object_name, l_object_id, l_created
14 LIMIT p_array_size;
15
16 for i in 1 .. l_owner.count
17 loop
18 l_processed_cnt := l_processed_cnt+1;
19 end loop;
20
21 exit when l_cursor%notfound;
22 end loop;
23 close l_cursor;
24
25 dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
26 end;
27 /

Procedure created.

ops$tkyte@ORA9I>
ops$tkyte@ORA9I> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I> exec process( 't2' );
processed 999 records


In 9iR2, it would be simplified to this:
ops$tkyte@ORA920> create or replace procedure process( p_tname in varchar2, p_array_size in number default 100 )
2 as
3 type array is table of t1%rowtype index by binary_integer;
4 l_data array;
5 l_cursor sys_refcursor;
6 l_processed_cnt number := 0;
7 begin
8 open l_cursor for select * from t1;
9 loop
10 fetch l_cursor BULK COLLECT into l_data limit p_array_size;
11 for i in 1 .. l_data.count
12 loop
13 l_processed_cnt := l_processed_cnt+1;
14 end loop;
15 exit when l_cursor%notfound;
16 end loop;
17 close l_cursor;
18 dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
19 end;
20 /

Procedure created.

ops$tkyte@ORA920> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec process( 't2' );
processed 499 records

PL/SQL procedure successfully completed.


Rating

  (13 ratings)

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

Comments

Exactly what was needed

Matt, August 12, 2003 - 10:18 am UTC

Tom,

Thanks for your quick and concise answer. This is exactly what I was looking for and written in such a way as to make it instantly applicable.

Thanks again.

Typo ?

Jay, August 12, 2003 - 11:33 am UTC

Tom, I am a little confused for your 2nd example, as I am not familiar with 9i at all. Your example hard code table t1, and the result also showed processed 499 instead of 999. Please advise.

Tom Kyte
August 12, 2003 - 11:57 am UTC

whoop.  change

 8      open l_cursor for select * from t1;

to

 8      open l_cursor for 'select * from '||p_tname;


ops$tkyte@ORA920> create or replace procedure process( p_tname in varchar2, p_array_size in number default 100 )
  2  as
  3      type array is table of t1%rowtype index by binary_integer;
  4      l_data   array;
  5      l_cursor sys_refcursor;
  6      l_processed_cnt number := 0;
  7  begin
  8      open l_cursor for 'select * from '||p_tname;
  9      loop
 10          fetch l_cursor BULK COLLECT into l_data limit p_array_size;
 11          for i in 1 .. l_data.count
 12          loop
 13              l_processed_cnt := l_processed_cnt+1;
 14          end loop;
 15          exit when l_cursor%notfound;
 16      end loop;
 17      close l_cursor;
 18      dbms_output.put_line( 'processed ' || l_processed_cnt || ' records' );
 19  end;
 20  /

Procedure created.

ops$tkyte@ORA920> exec process( 't1' );
processed 499 records

PL/SQL procedure successfully completed.

ops$tkyte@ORA920> exec process( 't2' );
processed 999 records

PL/SQL procedure successfully completed. 

DYNAMIC SQL

Santhanam, August 12, 2003 - 1:05 pm UTC

Good example

Good example for Nice feature

Keyan, August 13, 2003 - 11:33 am UTC

Tom

Thanks for very nice example.

Still not generic?

Phil, August 14, 2003 - 5:16 am UTC

Hi Tom,
Good example of 9.2 but there is still the reference in there to t1...

type array is table of t1%rowtype index by binary_integer



Tom Kyte
August 14, 2003 - 8:05 am UTC

so? i used t1 as the template to cut down on the code. I assumed that naturally there would be AT LEAST one table there I could rely on.



Nice But What If....

Jason, December 19, 2003 - 4:57 pm UTC

I need to return my cursor as an out parameter, in this example I cannot because it has to be of an sql type (error: PLS-00457)inorder to open into a cursor, if I create the needed types as db ojbects then I get the error "ORA-00932" when performing the fetch.

I need to be able to bulk collect into a table type and pass back the table type in the form of a cursor. Any ideas?

Tom Kyte
December 20, 2003 - 9:38 am UTC

in which example specifically are you talking about here?

since none of them are about returning ref cursors?

hows about an example of what you are trying to accomplish -- we can work with that.

my example

jason, December 22, 2003 - 10:49 am UTC

--here's my example
--I'm having conflicts between local and db defined types..

CREATE OR REPLACE TYPE type_record_object AS OBJECT
( num NUMBER,
dt DATE,
txt VARCHAR2(100));

CREATE TYPE type_table_ojbect AS TABLE OF type_record_object;
CREATE OR REPLACE PACKAGE a_cursor_test IS

TYPE type_cursor IS REF CURSOR;

TYPE type_private_record IS RECORD (num NUMBER,
dt DATE,
txt VARCHAR2(100));

TYPE type_private_table IS TABLE OF type_private_record;

PROCEDURE my_test (p_out_cursor OUT type_cursor);

END;
/

CREATE OR REPLACE PACKAGE BODY a_cursor_test IS

PROCEDURE my_test (p_out_cursor OUT type_cursor) IS
---local pkg defined types
v_cursor type_cursor;
v_record type_private_record;
v_table type_private_table := type_private_table();

--sql types (db objects)
v_record_object type_record_object;
v_table_object type_table_ojbect := type_table_ojbect();
v_sql_txt VARCHAR2(200):= 'SELECT 1, sysdate, ''one'' from dual';

CURSOR X IS SELECT 1 AS A, SYSDATE AS B, 'ONE' AS C FROM DUAL;

BEGIN

---try with local types
/*
here the type has to be local to use with dynamic sql
the problem is I can't open the curor with a local type
*/
EXECUTE IMMEDIATE v_sql_txt BULK COLLECT INTO v_table; --works
--next line will compile with error PLS-00642: Local collection types not allowed in sql statement
OPEN p_out_cursor FOR select * from table ( cast ( v_table as type_table_ojbect ) );--doesn't work



---try with db defined types
/*
Here the open cursor works because I'm using the db type, but my dynamic
sql does not work with the db type.
*/
--next line produces (ORA-00932 inconsistent datatypes) on call to proc.
EXECUTE IMMEDIATE v_sql_txt BULK COLLECT INTO v_table_object; --doesn't work
OPEN p_out_cursor FOR select * from table ( cast ( V_TABLE_OBJECT as type_table_ojbect ) ) ;--works

END;
END;
/

Tom Kyte
December 22, 2003 - 11:00 am UTC

ops$tkyte@ORA9IR2> CREATE OR REPLACE PACKAGE a_cursor_test
  2  IS
  3    TYPE type_cursor IS REF CURSOR;
  4
  5    PROCEDURE my_test (p_out_cursor OUT type_cursor);
  6  END;
  7  /
 
Package created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> CREATE OR REPLACE PACKAGE BODY a_cursor_test IS
  2
  3  PROCEDURE my_test (p_out_cursor OUT type_cursor)
  4  IS
  5      v_table_object  type_table_ojbect;
  6      v_sql_txt VARCHAR2(200):= 'SELECT type_record_object( 1, sysdate, ''one'' ) from dual';
  7  BEGIN
  8      EXECUTE IMMEDIATE
  9         'select cast( multiset( ' || v_sql_txt || ') as type_table_ojbect )  from dual'
 10      INTO v_table_object;
 11
 12      OPEN p_out_cursor
 13      FOR select *
 14            from table ( cast ( V_TABLE_OBJECT as type_table_ojbect ) );
 15    END;
 16  END;
 17  /
 
Package body created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> exec a_cursor_test.my_test( :x )
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> print x
 
       NUM DT        TXT
---------- --------- ------------------------------
         1 22-DEC-03 one


 

Work great (as usual)..

Jason, December 22, 2003 - 11:13 am UTC

Thanks a million.

Returning Table Type for a Dynamic select

Rajan, July 18, 2013 - 6:01 pm UTC

Hi Tom, the last example was very useful. But, only thing is I need to get the result set as a Table Type variable instead a refcursor. Please help on the below

create or replace type ty_dep_run_id as object
(run_id number,
   dependent_run_id number,
   curr_process varchar2(50),
   dep_process varchar2(50)
);


create or replace type tab_dep_run_id is table of ty_dep_run_id;


CREATE OR REPLACE FUNCTION fn_get_dependent_run (ip_run_id in number) RETURN 
tab_dep_run_id 
AS
  v_dep_tab tab_dep_run_id := tab_dep_run_id();
  v_sql VARCHAR2(4000);
BEGIN
  v_sql := 'with
        pc as 
          (select * from process_control),
        rp as 
          (select * from ref_process where effective_to = ''31-dec-9999'')
      SELECT hrchy.run_id, 
             hrchy.dependent_run_id,
             cur_rp.process_display_name curr_process,
             dep_rp.process_display_name dep_process
        FROM (SELECT pd.run_id,        
                     pd.dependent_run_id
                FROM process_dependency pd
                START WITH run_id = '||ip_run_id||'
                CONNECT BY prior pd.dependent_run_id = pd.run_id) hrchy,
             pc cur_pc,
             pc dep_pc,
             rp cur_rp,
             rp dep_rp
       WHERE hrchy.run_id = cur_pc.run_id
         AND hrchy.dependent_run_id = dep_pc.run_id
         AND cur_pc.process_id = cur_rp.process_id
         AND dep_pc.process_id = dep_rp.process_id
      order by hrchy.run_id desc';
  
  execute immediate ' SELECT cast(multiset('||v_sql||') as tab_dep_run_id) from dual' 
    bulk collect into v_dep_tab;
  return v_dep_tab;
END;

When I called this in a select like below
select  * from table(fn_get_dependent_run(1352));

I get below error
ora-00932: inconsisten datatypes: expected number got tab_dep_run_id
ora-06512: at "FN_GET_DEPENDENT_RUN", line 33

Please let me know whats wrong.
Tom Kyte
July 18, 2013 - 6:12 pm UTC

why are you using dynamic sql?

what are you using dynamic sql without using bind variables?

why don't you give me an entire example to work with so I can actually run, debug, modify and present code that functions?

please do not rely on NLS defaults, to_date( '31-dec-9999', 'dd-mon-yyyy' ) (and ugh, 9999!!!! effective_to should just be NULL.)

please do not have such a big query in there for an example either - your test case can and should be TEENY TINY - yet 100% complete (create tables and everything)




it looks like you are trying to take a query and fetch it into a collection of objects. there is NO NEED for any dynamic sql in your example - NONE.


it would work like this:


ops$tkyte%ORA11GR2> create or replace type myScalarType
  2  as object
  3  ( x varchar2(30),
  4    y date,
  5    z number
  6  )
  7  /

Type created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create or replace type myTableType
  2  as table of myScalarType
  3  /

Type created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create or replace function get_data( p_run_id in number ) return myTableType
  2  as
  3      l_data myTabletype;
  4  begin
  5      select myScalarType( username, created, user_id )
  6        bulk collect into l_data
  7        from all_users
  8       where user_id >= p_run_id;
  9
 10      return l_data;
 11  end;
 12  /

Function created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> declare
  2      l_data myTableType;
  3  begin
  4      l_data := get_data( 5 );
  5
  6      for i in 1 .. l_data.count
  7      loop
  8          dbms_output.put_line( l_data(i).x );
  9      end loop;
 10  end;
 11  /
OPS$MELANIE
BIG_TABLE
OPS$TKYTE
BI
PM
SH
IX
OE
HR
SCOTT
OWBSYS
APEX_030200
APEX_PUBLIC_USER
FLOWS_FILES
MGMT_VIEW
SYSMAN
SPATIAL_CSW_ADMIN_USR
SPATIAL_WFS_ADMIN_USR
MDDATA
OWBSYS_AUDIT
OLAPSYS
MDSYS
SI_INFORMTN_SCHEMA
ORDPLUGINS
ORDDATA
ORDSYS
ANONYMOUS
XDB
CTXSYS
EXFSYS
XS$NULL
WMSYS
APPQOSSYS
DBSNMP
ORACLE_OCM
DIP
OUTLN
SYSTEM

PL/SQL procedure successfully completed.



Returning Table Type for a Dynamic select

KRajan, July 18, 2013 - 7:17 pm UTC

Tom, Thanks for the lightning response.

This is required by the Front-end team. Whenever they pass a parent run_id, dependent run_ids and their dependents should be displayed. They need a simple select where they pass the parent run_id and they get all the dependents. So, I thought of putting them in a Table Type and return it from a function. The main query gets the parent run_id as input and returns all the dependent run_ids. Here is the simplified part of it with codes:

create table PROCESS_DEPENDENCY
(
  RUN_ID           NUMBER not null,
  DEPENDENT_RUN_ID NUMBER not null
);

insert into process_control values (1337,0);
insert into process_control values (1338,1337);
insert into process_control values (1339,1338);
insert into process_control values (1340,1339);
insert into process_control values (1341,1340);
insert into process_control values (1342,1340);
insert into process_control values (1343,1340);
insert into process_control values (1344,1340);
insert into process_control values (1345,1287);
insert into process_control values (1346,0);
insert into process_control values (1347,1346);
insert into process_control values (1348,1347);
insert into process_control values (1349,1348);
insert into process_control values (1350,1340);
insert into process_control values (1351,1349);
insert into process_control values (1352,1351);
insert into process_control values (1353,0);
insert into process_control values (1354,1353);
insert into process_control values (1355,1354);
insert into process_control values (1356,1355);
insert into process_control values (1357,0);
insert into process_control values (1358,1357);
insert into process_control values (1359,1358);
insert into process_control values (1360,1359);

create or replace type ty_dep_run_id as object
(
   run_id number,
   dependent_run_id number
);

create or replace type tab_dep_run_id is table of ty_dep_run_id;

CREATE OR REPLACE FUNCTION fn_get_dependent_run (ip_run_id in number) RETURN 
tab_dep_run_id 
AS

  v_dep_tab tab_dep_run_id := tab_dep_run_id();
  v_sql VARCHAR2(4000);

BEGIN
  v_sql := 'SELECT pd.run_id,        
                     pd.dependent_run_id
                FROM cpm_data.process_dependency pd
                START WITH run_id = '||ip_run_id||'
                CONNECT BY prior pd.dependent_run_id = pd.run_id';
  
  execute immediate ' SELECT cast(multiset('||v_sql||') as tab_dep_run_id) from dual' 
    bulk collect into v_dep_tab;
  return v_dep_tab;
END;

I donot want to display them in a dbms_output. I wanted it in a select as an output.
select  * from table(fn_get_dependent_run(1352));

The above should return the hierarchy from top to bottom. Please help.
Tom Kyte
July 19, 2013 - 12:58 pm UTC

why do you believe you need dynamic sql for this??????????????????????????????????????????????????????????????


ops$tkyte%ORA11GR2> CREATE OR REPLACE FUNCTION fn_get_dependent_run (ip_run_id in number)
  2  RETURN tab_dep_run_id
  3  AS
  4
  5    v_dep_tab tab_dep_run_id;
  6
  7  BEGIN
  8      select ty_dep_run_id( pd.run_id, pd.dependent_run_id)
  9        bulk collect into v_dep_tab
 10        FROM process_dependency pd
 11       START WITH run_id = fn_get_dependent_run.ip_run_id
 12     CONNECT BY prior pd.dependent_run_id = pd.run_id;
 13
 14     return v_dep_tab;
 15  END;
 16  /

Function created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> declare
  2      l_data tab_dep_run_id;
  3  begin
  4      l_data := fn_get_dependent_run( 1352 );
  5      dbms_output.put_line( l_data.count || ' entries' );
  6      for i in 1 .. l_data.count
  7      loop
  8          dbms_output.put_line( l_data(i).run_id || ', ' || l_data(i).dependent_run_id );
  9      end loop;
 10  end;
 11  /
6 entries
1352, 1351
1351, 1349
1349, 1348
1348, 1347
1347, 1346
1346, 0

PL/SQL procedure successfully completed.



you do not need, want, nor desire dynamic sql.


you have a query, it has inputs (run id). Just code static sql using bind variables like I've been demonstrating.

Returning Table Type for a Dynamic select

KRajan, July 18, 2013 - 7:57 pm UTC

Tom, please use the below codes.
create table PROCESS_DEPENDENCY
(
  RUN_ID           NUMBER not null,
  DEPENDENT_RUN_ID NUMBER not null
);
/

insert into PROCESS_DEPENDENCY values (1337,0);
insert into PROCESS_DEPENDENCY values (1338,1337);
insert into PROCESS_DEPENDENCY values (1339,1338);
insert into PROCESS_DEPENDENCY values (1340,1339);
insert into PROCESS_DEPENDENCY values (1341,1340);
insert into PROCESS_DEPENDENCY values (1342,1340);
insert into PROCESS_DEPENDENCY values (1343,1340);
insert into PROCESS_DEPENDENCY values (1344,1340);
insert into PROCESS_DEPENDENCY values (1345,1287);
insert into PROCESS_DEPENDENCY values (1346,0);
insert into PROCESS_DEPENDENCY values (1347,1346);
insert into PROCESS_DEPENDENCY values (1348,1347);
insert into PROCESS_DEPENDENCY values (1349,1348);
insert into PROCESS_DEPENDENCY values (1350,1340);
insert into PROCESS_DEPENDENCY values (1351,1349);
insert into PROCESS_DEPENDENCY values (1352,1351);
insert into PROCESS_DEPENDENCY values (1353,0);
insert into PROCESS_DEPENDENCY values (1354,1353);
insert into PROCESS_DEPENDENCY values (1355,1354);
insert into PROCESS_DEPENDENCY values (1356,1355);
insert into PROCESS_DEPENDENCY values (1357,0);
insert into PROCESS_DEPENDENCY values (1358,1357);
insert into PROCESS_DEPENDENCY values (1359,1358);
insert into PROCESS_DEPENDENCY values (1360,1359);

create or replace type ty_dep_run_id as object
(
   run_id number,
   dependent_run_id number
);
/

create or replace type tab_dep_run_id is table of ty_dep_run_id;
/

CREATE OR REPLACE FUNCTION fn_get_dependent_run (ip_run_id in number) RETURN 
tab_dep_run_id 
AS

  v_dep_tab tab_dep_run_id := tab_dep_run_id();
  v_sql VARCHAR2(4000);

BEGIN
  v_sql := 'SELECT pd.run_id,        
                     pd.dependent_run_id
                FROM process_dependency pd
                START WITH run_id = '||ip_run_id||'
                CONNECT BY prior pd.dependent_run_id = pd.run_id';
  
  execute immediate ' SELECT cast(multiset('||v_sql||') as tab_dep_run_id) from dual' 
    bulk collect into v_dep_tab;
  return v_dep_tab;

END;
/

Tom Kyte
July 19, 2013 - 1:00 pm UTC

see above

Thanks!!!!

Rajan, July 19, 2013 - 1:56 pm UTC

Thanks a lot Tom!!!! I got lot to learn there..

Followup on the above post

Devender Rawat., January 07, 2015 - 6:45 am UTC

Actually found this post very useful, as even i am facing the same issue that oracle doesn't understand a variable next to cast(multiset(V_sql)) function in this case :
select cast( multiset( ' || v_sql|| ') as type_table_ojbect ) from dual
and the same if you replace the v_sql with the actual sql statement in the variable it works.
Any help on this would be appreciated.

Thanks,
Devender Rawat.

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