Skip to Main Content
  • Questions
  • Return table type from package function using dynamic SQL

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Daniel Overby.

Asked: October 22, 2015 - 12:01 pm UTC

Last updated: October 22, 2015 - 4:25 pm UTC

Version: Oracle RDBMS Enterprise Edition 12.1.0.2

Viewed 10K+ times! This question is

You Asked

I would like to generate a SQL statement within a package function and bulk collect the records into a table type returned by the function. I then need to get the data using regular SQL (SELECT * FROM TABLE(…)).

I get an ORA-00932 error when using dynamic SQL and EXECUTE IMMEDIATE … BULK COLLECT INTO … However, if I write the SQL state completely in the function, then it works. To avoid duplicate code, I would to be able to construct the SQL dynamically based on an input to the function.

For performance reasons it is a requirement to use BULK COLLECT.

Test case:

CREATE TABLE foobar AS SELECT last_ddl_time, object_name FROM dba_objects;
DROP TYPE t_table;
DROP TYPE t_record;
CREATE TYPE t_record IS OBJECT( ts DATE, name VARCHAR2 (50) );
CREATE TYPE t_table IS TABLE OF t_record;

CREATE OR REPLACE PACKAGE foobar_pkg AS
   FUNCTION nogood (v_input NUMBER)
      RETURN t_table;

   FUNCTION good1
      RETURN t_table;

   FUNCTION good2
      RETURN t_table;

END;

CREATE OR REPLACE PACKAGE BODY foobar_pkg AS
   FUNCTION good1
      RETURN t_table IS
      l_table   t_table := t_table ();
   BEGIN
      SELECT t_record (last_ddl_time, 'option 1: ' || object_name)
        BULK COLLECT INTO l_table
        FROM foobar;

      RETURN l_table;
   END;

   FUNCTION good2
      RETURN t_table IS
      l_table   t_table := t_table ();
   BEGIN
      SELECT t_record (last_ddl_time, 'option 2: ' || object_name)
        BULK COLLECT INTO l_table
        FROM foobar;

      RETURN l_table;
   END;

   FUNCTION nogood (v_input NUMBER)
      RETURN t_table IS
      l_sql     VARCHAR2 (500);
      l_table   t_table := t_table ();
   BEGIN
      IF v_input = 1 THEN
         l_sql := 'SELECT last_ddl_time, ''option 1: '' || object_name FROM foobar';
      ELSE
         l_sql := 'SELECT last_ddl_time, ''option 2: '' || object_name FROM foobar';
      END IF;

      EXECUTE IMMEDIATE l_sql BULK COLLECT INTO l_table;

      RETURN l_table;
   END;

END;

SELECT * FROM TABLE (foobar_pkg.nogood (1));
SELECT * FROM TABLE (foobar_pkg.nogood (2));
SELECT * FROM TABLE (foobar_pkg.good1);
SELECT * FROM TABLE (foobar_pkg.good2);

and Chris said...

Dynamic SQL in table functions works just fine. You just need to place the fields in a call to t_record, just like your static SQL does:

CREATE OR REPLACE PACKAGE BODY foobar_pkg AS
   FUNCTION good1
      RETURN t_table IS
      l_table   t_table := t_table ();
   BEGIN
      SELECT t_record (last_ddl_time, 'option 1: ' || object_name)
        BULK COLLECT INTO l_table
        FROM foobar;

      RETURN l_table;
   END;

   FUNCTION good2
      RETURN t_table IS
      l_table   t_table := t_table ();
   BEGIN
      SELECT t_record (last_ddl_time, 'option 2: ' || object_name)
        BULK COLLECT INTO l_table
        FROM foobar;

      RETURN l_table;
   END;

   FUNCTION nogood (v_input NUMBER)
      RETURN t_table IS
      l_sql     VARCHAR2 (500);
      l_table   t_table := t_table ();
   BEGIN
      IF v_input = 1 THEN
         --  added t_record call
         l_sql := 'SELECT t_record (last_ddl_time, ''option 1: '' || object_name) FROM foobar';
      ELSE
         --  added t_record call
         l_sql := 'SELECT t_record (last_ddl_time, ''option 2: '' || object_name) FROM foobar';
      END IF;

      EXECUTE IMMEDIATE l_sql BULK COLLECT INTO l_table;

      RETURN l_table;
   END;

END;
/

SELECT * FROM TABLE (foobar_pkg.nogood (1))
WHERE  ROWNUM = 1;

TS                NAME                         
----------------- ------------------------------
07 Jul 2014 06:11 option 1: ICOL$               

SELECT * FROM TABLE (foobar_pkg.nogood (2))
WHERE  ROWNUM = 1;

TS                NAME                         
----------------- ------------------------------
07 Jul 2014 06:11 option 2: ICOL$    

Rating

  (2 ratings)

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

Comments

Why not just use ref cursors?

Heath, October 22, 2015 - 3:12 pm UTC

Wouldn't it be simpler to just return the results via REF CURSOR instead of creating and populating arrays?

It would perform better as well.
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:246014735810
Chris Saxon
October 22, 2015 - 4:25 pm UTC

The poster wanted to use the results in a table function in SQL. These must return nested tables or varrays:

http://docs.oracle.com/database/121/LNPLS/tuning.htm#LNPLS915

Admittedly it's not clear why they want to do this instead of just writing all the SQL in one statement...

Works like a charm

Daniel Overby Hansen, October 23, 2015 - 9:33 am UTC

Thanks for helping out. That solved our problem, and performance is good.
The PL/SQL package servers as an interface between two systems that want data in a specific, non-flexible way.

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