Chaining Pipelined Table Functions for Multiple Transformations
Chaining pipelined table functions is an efficient way to perform multiple transformations on data.
Note:
You cannot run a pipelined table function over a database link. The reason is that the return type of a pipelined table function is a SQL user-defined type, which can be used only in a single database (as explained in Oracle Database Object-Relational Developer's Guide). Although the return type of a pipelined table function might appear to be a PL/SQL type, the database actually converts that PL/SQL type to a corresponding SQL user-defined type.
Topics
Overview of Table Functions
A table function is a user-defined PL/SQL function that returns a collection of rows (an associative array, nested table or varray).
You can select from this collection as if it were a database table by invoking the table function inside the TABLE
clause in a SELECT
statement. The TABLE operator is optional.
For example:
SELECT * FROM TABLE(table_function_name(parameter_list))
Alternatively, the same query can be written without the TABLE operator as follow:
SELECT * FROM table_function_name(parameter_list)
A table function can take a collection of rows as input (that is, it can have an input parameter that is a nested table, varray, or cursor variable). Therefore, output from table function tf1
can be input to table function tf2
, and output from tf2
can be input to table function tf3
, and so on.
To improve the performance of a table function, you can:
-
Enable the function for parallel execution, with the
PARALLEL_ENABLE
option.Functions enabled for parallel execution can run concurrently.
-
Stream the function results directly to the next process, with Oracle Streams.
Streaming eliminates intermediate staging between processes.
-
Pipeline the function results, with the
PIPELINED
option.A pipelined table function returns a row to its invoker immediately after processing that row and continues to process rows. Response time improves because the entire collection need not be constructed and returned to the server before the query can return a single result row. (Also, the function needs less memory, because the object cache need not materialize the entire collection.)
Caution:
A pipelined table function always references the current state of the data. If the data in the collection changes after the cursor opens for the collection, then the cursor reflects the changes. PL/SQL variables are private to a session and are not transactional. Therefore, read consistency, well known for its applicability to table data, does not apply to PL/SQL collection variables.
See Also:
-
Oracle Database SQL Language Reference for more information about the
TABLE
clause of theSELECT
statement -
Oracle Database Data Cartridge Developer's Guide for information about using pipelined and parallel table functions
Creating Pipelined Table Functions
A pipelined table function must be either a standalone function or a package function.
PIPELINED Option (Required)
For a standalone function, specify the PIPELINED
option in the CREATE
FUNCTION
statement (for syntax, see "CREATE FUNCTION Statement"). For a package function, specify the PIPELINED
option in both the function declaration and function definition (for syntax, see "Function Declaration and Definition").
PARALLEL_ENABLE Option (Recommended)
To improve its performance, enable the pipelined table function for parallel execution by specifying the PARALLEL_ENABLE
option.
AUTONOMOUS_TRANSACTION Pragma
If the pipelined table function runs DML statements, then make it autonomous, with the AUTONOMOUS_TRANSACTION
pragma (described in "AUTONOMOUS_TRANSACTION Pragma"). Then, during parallel execution, each instance of the function creates an independent transaction.
DETERMINISTIC Option (Recommended)
Multiple invocations of a pipelined table function, in either the same query or separate queries, cause multiple executions of the underlying implementation. If the function is deterministic, specify the DETERMINISTIC
option, described in "DETERMINISTIC Clause".
Parameters
Typically, a pipelined table function has one or more cursor variable parameters. For information about cursor variables as function parameters, see "Cursor Variables as Subprogram Parameters".
See Also:
-
"Cursor Variables" for general information about cursor variables
-
"Subprogram Parameters" for general information about subprogram parameters
RETURN Data Type
The data type of the value that a pipelined table function returns must be a
collection type defined either at schema level or inside a package (therefore, it cannot be
an associative array type). The elements of the collection type must be SQL data types, not
data types supported only by PL/SQL (such as PLS_INTEGER
). For information
about collection types, see "Collection Types". For
information about SQL data types, see Oracle Database SQL Language Reference.
You can use SQL data types ANYTYPE
, ANYDATA
, and ANYDATASET
to dynamically encapsulate and access type descriptions, data instances, and sets of data instances of any other SQL type, including object and collection types. You can also use these types to create unnamed types, including anonymous collection types. For information about these types, see Oracle Database PL/SQL Packages and Types Reference.
PIPE ROW Statement
Inside a pipelined table function, use the PIPE
ROW
statement to return a collection element to the invoker without returning control to the invoker. See "PIPE ROW Statement" for its syntax and semantics.
RETURN Statement
As in every function, every execution path in a pipelined table function must lead to a RETURN
statement, which returns control to the invoker. However, in a pipelined table function, a RETURN
statement need not return a value to the invoker. See "RETURN Statement" for its syntax and semantics.
Example
Example 13-30 Creating and Invoking Pipelined Table Function
This example creates a package that includes a pipelined table function, f1
, and then selects from the collection of rows that f1
returns.
CREATE OR REPLACE PACKAGE pkg1 AUTHID DEFINER AS
TYPE numset_t IS TABLE OF NUMBER;
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED;
END pkg1;
/
Create a pipelined table function f1 that returns a collection of elements (1,2,3,... x).
CREATE OR REPLACE PACKAGE BODY pkg1 AS
FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED IS
BEGIN
FOR i IN 1..x LOOP
PIPE ROW(i);
END LOOP;
RETURN;
END f1;
END pkg1;
/
SELECT * FROM TABLE(pkg1.f1(5));
Result:
COLUMN_VALUE
------------
1
2
3
4
5
5 rows selected.
SELECT * FROM pkg1.f1(2);
Result:
COLUMN_VALUE
------------
1
2
Pipelined Table Functions as Transformation Functions
A pipelined table function with a cursor variable parameter can serve as a transformation function. Using the cursor variable, the function fetches an input row. Using the PIPE
ROW
statement, the function pipes the transformed row or rows to the invoker. If the FETCH
and PIPE
ROW
statements are inside a LOOP
statement, the function can transform multiple input rows.
In Example 13-31, the pipelined table function transforms each selected row of the employees
table to two nested table rows, which it pipes to the SELECT
statement that invokes it. The actual parameter that corresponds to the formal cursor variable parameter is a CURSOR
expression; for information about these, see "Passing CURSOR Expressions to Pipelined Table Functions".
Example 13-31 Pipelined Table Function Transforms Each Row to Two Rows
CREATE OR REPLACE PACKAGE refcur_pkg AUTHID DEFINER IS
TYPE refcur_t IS REF CURSOR RETURN employees%ROWTYPE;
TYPE outrec_typ IS RECORD (
var_num NUMBER(6),
var_char1 VARCHAR2(30),
var_char2 VARCHAR2(30)
);
TYPE outrecset IS TABLE OF outrec_typ;
FUNCTION f_trans (p refcur_t) RETURN outrecset PIPELINED;
END refcur_pkg;
/
CREATE OR REPLACE PACKAGE BODY refcur_pkg IS
FUNCTION f_trans (p refcur_t) RETURN outrecset PIPELINED IS
out_rec outrec_typ;
in_rec p%ROWTYPE;
BEGIN
LOOP
FETCH p INTO in_rec; -- input row
EXIT WHEN p%NOTFOUND;
out_rec.var_num := in_rec.employee_id;
out_rec.var_char1 := in_rec.first_name;
out_rec.var_char2 := in_rec.last_name;
PIPE ROW(out_rec); -- first transformed output row
out_rec.var_char1 := in_rec.email;
out_rec.var_char2 := in_rec.phone_number;
PIPE ROW(out_rec); -- second transformed output row
END LOOP;
CLOSE p;
RETURN;
END f_trans;
END refcur_pkg;
/
SELECT * FROM TABLE (
refcur_pkg.f_trans (
CURSOR (SELECT * FROM employees WHERE department_id = 60)
)
);
Result:
VAR_NUM VAR_CHAR1 VAR_CHAR2
---------- ------------------------------ ------------------------------
103 Alexander James
103 AJAMES 1.590.555.0103
104 Bruce Miller
104 BMILLER 1.590.555.0104
105 David Williams
105 DWILLIAMS 1.590.555.0105
106 Valli Jackson
106 VJACKSON 1.590.555.0106
107 Diana Nguyen
107 DNGUYEN 1.590.555.0107
10 rows selected.
Chaining Pipelined Table Functions
To chain pipelined table functions tf1
and tf2
is to make the output of tf1
the input of tf2
. For example:
SELECT * FROM TABLE(tf2(CURSOR(SELECT * FROM TABLE(tf1()))));
The rows that tf1
pipes out must be compatible actual parameters for the formal input parameters of tf2
.
If chained pipelined table functions are enabled for parallel execution, then each function runs in a different process (or set of processes).
Fetching from Results of Pipelined Table Functions
You can associate a named cursor with a query that invokes a pipelined table function. Such a cursor has no special fetch semantics, and such a cursor variable has no special assignment semantics.
However, the SQL optimizer does not optimize across PL/SQL statements. Therefore, in Example 13-32, the first PL/SQL statement is slower than the second—despite the overhead of running two SQL statements in the second PL/SQL statement, and even if function results are piped between the two SQL statements in the first PL/SQL statement.
In Example 13-32, assume that f
and g
are pipelined table functions, and that each function accepts a cursor variable parameter. The first PL/SQL statement associates cursor variable r
with a query that invokes f
, and then passes r
to g
. The second PL/SQL statement passes CURSOR
expressions to both f
and g
.
See Also:
Example 13-32 Fetching from Results of Pipelined Table Functions
DECLARE r SYS_REFCURSOR; ... -- First PL/SQL statement (slower): BEGIN OPEN r FOR SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab))); SELECT * BULK COLLECT INTO rec_tab FROM TABLE(g(r)); -- NOTE: When g completes, it closes r. END; -- Second PL/SQL statement (faster): SELECT * FROM TABLE(g(CURSOR(SELECT * FROM TABLE(f(CURSOR(SELECT * FROM tab)))))); /
Passing CURSOR Expressions to Pipelined Table Functions
As Example 13-32 shows, the actual parameter for the cursor variable parameter of a pipelined table function can be either a cursor variable or a CURSOR
expression, and the latter is more efficient.
Note:
When a SQL SELECT
statement passes a CURSOR
expression to a function, the referenced cursor opens when the function begins to run and closes when the function completes.
See Also:
"CURSOR Expressions" for general information about CURSOR
expressions
Example 13-33 creates a package that includes a pipelined table function with two cursor variable parameters and then invokes the function in a SELECT
statement, using CURSOR
expressions for actual parameters.
Example 13-34 uses a pipelined table function as an aggregate function, which takes a set of input rows and returns a single result. The SELECT
statement selects the function result. (For information about the pseudocolumn COLUMN_VALUE
, see Oracle Database SQL Language Reference.)
Example 13-33 Pipelined Table Function with Two Cursor Variable Parameters
CREATE OR REPLACE PACKAGE refcur_pkg AUTHID DEFINER IS
TYPE refcur_t1 IS REF CURSOR RETURN employees%ROWTYPE;
TYPE refcur_t2 IS REF CURSOR RETURN departments%ROWTYPE;
TYPE outrec_typ IS RECORD (
var_num NUMBER(6),
var_char1 VARCHAR2(30),
var_char2 VARCHAR2(30)
);
TYPE outrecset IS TABLE OF outrec_typ;
FUNCTION g_trans (p1 refcur_t1, p2 refcur_t2) RETURN outrecset PIPELINED;
END refcur_pkg;
/
CREATE OR REPLACE PACKAGE BODY refcur_pkg IS
FUNCTION g_trans (
p1 refcur_t1,
p2 refcur_t2
) RETURN outrecset PIPELINED
IS
out_rec outrec_typ;
in_rec1 p1%ROWTYPE;
in_rec2 p2%ROWTYPE;
BEGIN
LOOP
FETCH p2 INTO in_rec2;
EXIT WHEN p2%NOTFOUND;
END LOOP;
CLOSE p2;
LOOP
FETCH p1 INTO in_rec1;
EXIT WHEN p1%NOTFOUND;
-- first row
out_rec.var_num := in_rec1.employee_id;
out_rec.var_char1 := in_rec1.first_name;
out_rec.var_char2 := in_rec1.last_name;
PIPE ROW(out_rec);
-- second row
out_rec.var_num := in_rec2.department_id;
out_rec.var_char1 := in_rec2.department_name;
out_rec.var_char2 := TO_CHAR(in_rec2.location_id);
PIPE ROW(out_rec);
END LOOP;
CLOSE p1;
RETURN;
END g_trans;
END refcur_pkg;
/
SELECT * FROM TABLE (
refcur_pkg.g_trans (
CURSOR (SELECT * FROM employees WHERE department_id = 60),
CURSOR (SELECT * FROM departments WHERE department_id = 60)
)
);
Result:
VAR_NUM VAR_CHAR1 VAR_CHAR2
---------- ------------------------------ ------------------------------
103 Alexander James
60 IT 1400
104 Bruce Miller
60 IT 1400
105 David Williams
60 IT 1400
106 Valli Jackson
60 IT 1400
107 Diana Nguyen
60 IT 1400
10 rows selected.
Example 13-34 Pipelined Table Function as Aggregate Function
DROP TABLE gradereport;
CREATE TABLE gradereport (
student VARCHAR2(30),
subject VARCHAR2(30),
weight NUMBER,
grade NUMBER
);
INSERT INTO gradereport (student, subject, weight, grade)
VALUES ('Mark', 'Physics', 4, 4);
INSERT INTO gradereport (student, subject, weight, grade)
VALUES ('Mark','Chemistry', 4, 3);
INSERT INTO gradereport (student, subject, weight, grade)
VALUES ('Mark','Maths', 3, 3);
INSERT INTO gradereport (student, subject, weight, grade)
VALUES ('Mark','Economics', 3, 4);
CREATE OR REPLACE PACKAGE pkg_gpa AUTHID DEFINER IS
TYPE gpa IS TABLE OF NUMBER;
FUNCTION weighted_average(input_values SYS_REFCURSOR)
RETURN gpa PIPELINED;
END pkg_gpa;
/
CREATE OR REPLACE PACKAGE BODY pkg_gpa IS
FUNCTION weighted_average (input_values SYS_REFCURSOR)
RETURN gpa PIPELINED
IS
grade NUMBER;
total NUMBER := 0;
total_weight NUMBER := 0;
weight NUMBER := 0;
BEGIN
LOOP
FETCH input_values INTO weight, grade;
EXIT WHEN input_values%NOTFOUND;
total_weight := total_weight + weight; -- Accumulate weighted average
total := total + grade*weight;
END LOOP;
PIPE ROW (total / total_weight);
RETURN; -- returns single result
END weighted_average;
END pkg_gpa;
/
This query shows how the table function can be invoked without the optional TABLE operator.
SELECT w.column_value "weighted result"
FROM pkg_gpa.weighted_average (
CURSOR (SELECT weight, grade FROM gradereport)
) w;
Result:
weighted result
---------------
3.5
1 row selected.
DML Statements on Pipelined Table Function Results
The "table" that a pipelined table function returns cannot be the target table of a DELETE
, INSERT
, UPDATE
, or MERGE
statement. However, you can create a view of such a table and create INSTEAD
OF
triggers on the view. For information about INSTEAD
OF
triggers, see "INSTEAD OF DML Triggers".
See Also:
Oracle Database SQL Language Reference for information about the CREATE
VIEW
statement
NO_DATA_NEEDED Exception
You must understand the predefined exception NO_DATA_NEEDED
in two cases:
-
You include an
OTHERS
exception handler in a block that includes aPIPE
ROW
statement -
Your code that feeds a
PIPE
ROW
statement must be followed by a clean-up procedureTypically, the clean-up procedure releases resources that the code no longer needs.
When the invoker of a pipelined table function needs no more rows from the function, the PIPE
ROW
statement raises NO_DATA_NEEDED
. If the pipelined table function does not handle NO_DATA_NEEDED
, as in Example 13-35, then the function invocation terminates but the invoking statement does not terminate. If the pipelined table function handles NO_DATA_NEEDED
, its exception handler can release the resources that it no longer needs, as in Example 13-36.
In Example 13-35, the pipelined table function pipe_rows
does not handle the NO_DATA_NEEDED
exception. The SELECT
statement that invokes pipe_rows
needs only four rows. Therefore, during the fifth invocation of pipe_rows
, the PIPE
ROW
statement raises the exception NO_DATA_NEEDED
. The fifth invocation of pipe_rows
terminates, but the SELECT
statement does not terminate.
If the exception-handling part of a block that includes a PIPE
ROW
statement includes an OTHERS
exception handler to handle unexpected exceptions, then it must also include an exception handler for the expected NO_DATA_NEEDED
exception. Otherwise, the OTHERS
exception handler handles the NO_DATA_NEEDED
exception, treating it as an unexpected error. The following exception handler reraises the NO_DATA_NEEDED
exception, instead of treating it as a irrecoverable error:
EXCEPTION WHEN NO_DATA_NEEDED THEN RAISE; WHEN OTHERS THEN -- (Put error-logging code here) RAISE_APPLICATION_ERROR(-20000, 'Irrecoverable error.'); END;
In Example 13-36, assume that the package External_Source
contains these public items:
-
Procedure
Init
, which allocates and initializes the resources thatNext_Row
needs -
Function
Next_Row
, which returns some data from a specific external source and raises the user-defined exceptionDone
(which is also a public item in the package) when the external source has no more data -
Procedure
Clean_Up
, which releases the resources thatInit
allocated
The pipelined table function get_external_source_data
pipes rows from the external source by invoking External_Source.Next_Row
until either:
-
The external source has no more rows.
In this case, the
External_Source.Next_Row
function raises the user-defined exceptionExternal_Source.Done
. -
get_external_source_data
needs no more rows.In this case, the
PIPE
ROW
statement inget_external_source_data
raises theNO_DATA_NEEDED
exception.
In either case, an exception handler in block b
in get_external_source_data
invokes External_Source.Clean_Up
, which releases the resources that Next_Row
was using.
Example 13-35 Pipelined Table Function Does Not Handle NO_DATA_NEEDED
CREATE TYPE t IS TABLE OF NUMBER / CREATE OR REPLACE FUNCTION pipe_rows RETURN t PIPELINED AUTHID DEFINER IS n NUMBER := 0; BEGIN LOOP n := n + 1; PIPE ROW (n); END LOOP; END pipe_rows; / SELECT COLUMN_VALUE FROM TABLE(pipe_rows()) WHERE ROWNUM < 5 /
Result:
COLUMN_VALUE ------------ 1 2 3 4 4 rows selected.
Example 13-36 Pipelined Table Function Handles NO_DATA_NEEDED
CREATE OR REPLACE FUNCTION get_external_source_data RETURN t PIPELINED AUTHID DEFINER IS BEGIN External_Source.Init(); -- Initialize. <<b>> BEGIN LOOP -- Pipe rows from external source. PIPE ROW (External_Source.Next_Row()); END LOOP; EXCEPTION WHEN External_Source.Done THEN -- When no more rows are available, External_Source.Clean_Up(); -- clean up. WHEN NO_DATA_NEEDED THEN -- When no more rows are needed, External_Source.Clean_Up(); -- clean up. RAISE NO_DATA_NEEDED; -- Optional, equivalent to RETURN. END b; END get_external_source_data; /