This chapter explains how the PL/SQL compiler optimizes your code and how to write efficient PL/SQL code and improve existing PL/SQL code.
Prior to Oracle Database 10g, the PL/SQL compiler translated your source text to system code without applying many changes to improve performance. Now, PL/SQL uses an optimizer that can rearrange code for better performance.
The optimizer is enabled by default. In rare cases, if the overhead of the optimizer makes compilation of very large applications too slow, you can lower the optimization by setting the compilation parameter PLSQL_OPTIMIZE_LEVEL=1
instead of its default value 2. In even rarer cases, PL/SQL might raise an exception earlier than expected or not at all. Setting PLSQL_OPTIMIZE_LEVEL=1
prevents the code from being rearranged.
See Also:
Oracle Database Reference for information about the PLSQL_OPTIMIZE_LEVEL
compilation parameter
Oracle Database Reference for information about the static dictionary view ALL_PLSQL_OBJECT_SETTINGS
One optimization that the compiler can perform is subprogram inlining. Subprogram inlining replaces a subprogram invocation with a copy of the invoked subprogram (if the invoked and invoking subprograms are in the same program unit). To allow subprogram inlining, either accept the default value of the PLSQL_OPTIMIZE_LEVEL
compilation parameter (which is 2) or set it to 3.
With PLSQL_OPTIMIZE_LEVEL=2
, you must specify each subprogram to be inlined with the INLINE
pragma:
PRAGMA INLINE (subprogram, 'YES')
If subprogram
is overloaded, then the preceding pragma applies to every subprogram with that name.
With PLSQL_OPTIMIZE_LEVEL=3
, the PL/SQL compiler seeks opportunities to inline subprograms. You need not specify subprograms to be inlined. However, you can use the INLINE
pragma (with the preceding syntax) to give a subprogram a high priority for inlining, and then the compiler inlines it unless other considerations or limits make the inlining undesirable.
If a particular subprogram is inlined, performance almost always improves. However, because the compiler inlines subprograms early in the optimization process, it is possible for subprogram inlining to preclude later, more powerful optimizations.
If subprogram inlining slows the performance of a particular PL/SQL program, then use the PL/SQL hierarchical profiler (explained in Oracle Database Advanced Application Developer's Guide) to identify subprograms for which you want to turn off inlining. To turn off inlining for a subprogram, use the INLINE
pragma:
PRAGMA INLINE (subprogram, 'NO')
The INLINE
pragma affects only the immediately following declaration or statement, and only some kinds of statements.
When the INLINE
pragma immediately precedes a declaration, it affects:
Every invocation of the specified subprogram in that declaration
Every initialization value in that declaration except the default initialization values of records
When the INLINE
pragma immediately precedes one of these statements, the pragma affects every invocation of the specified subprogram in that statement:
Assignment
CALL
Conditional
CASE
CONTINUE
WHEN
EXECUTE
IMMEDIATE
EXIT
WHEN
LOOP
RETURN
The INLINE
pragma does not affect statements that are not in the preceding list.
In Example 12-1, if PLSQL_OPTIMIZE_LEVEL=2
, the INLINE
pragma affects the procedure invocations p1(1)
and p1(2)
, but not the procedure invocations p1(3)
and p1(4)
.
Example 12-1 Specifying that Subprogram Is To Be Inlined
PROCEDURE p1 (x PLS_INTEGER) IS ...
...
PRAGMA INLINE (p1, 'YES');
x:= p1(1) + p1(2) + 17; -- These 2 invocations to p1 are inlined
...
x:= p1(3) + p1(4) + 17; -- These 2 invocations to p1 are not inlined
...
In Example 12-2, if PLSQL_OPTIMIZE_LEVEL=2
, the INLINE
pragma affects both functions named p2
.
Example 12-2 Specifying that Overloaded Subprogram Is To Be Inlined
FUNCTION p2 (p boolean) return PLS_INTEGER IS ...
FUNCTION p2 (x PLS_INTEGER) return PLS_INTEGER IS ...
...
PRAGMA INLINE(p2, 'YES');
x := p2(true) + p2(3);
...
In Example 12-3, the INLINE
pragma affects the procedure invocations p1(1)
and p1(2)
, but not the procedure invocations p1(3)
and p1(4)
.
Example 12-3 Specifying that Subprogram Is Not To Be Inlined
PROCEDURE p1 (x PLS_INTEGER) IS ...
...
PRAGMA INLINE (p1, 'NO');
x:= p1(1) + p1(2) + 17; -- These 2 invocations to p1 are not inlined
...
x:= p1(3) + p1(4) + 17; -- These 2 invocations to p1 might be inlined
...
Multiple pragmas can affect the same declaration or statement. Each pragma applies its own effect to the statement. If PRAGMA
INLINE(
subprogram,
'YES')
and PRAGMA
INLINE(
identifier,
'NO')
have the same subprogram
, then 'NO'
overrides 'YES'
. One PRAGMA
INLINE(
subprogram,
'NO')
overrides any number of occurrences of PRAGMA
INLINE(
subprogram,
'YES')
, and the order of these pragmas is not important.
In Example 12-4, the second INLINE
pragma overrides both the first and third INLINE
pragmas.
Example 12-4 PRAGMA INLINE ... 'NO' Overrides PRAGMA INLINE ... 'YES'
PROCEDURE p1 (x PLS_INTEGER) IS ... ... PRAGMA INLINE (p1, 'YES'); PRAGMA INLINE (p1, 'NO'); PRAGMA INLINE (p1, 'YES'); x:= p1(1) + p1(2) + 17; -- These 2 invocations to p1 are not inlined ...
See Also:
Oracle Database Reference for information about the PLSQL_OPTIMIZE_LEVEL
compilation parameter
Oracle Database Reference for information about the static dictionary view ALL_PLSQL_OBJECT_SETTINGS
The following kinds of PL/SQL code are very likely to benefit from tuning:
Older code that does not take advantage of new PL/SQL language features.
For information about new PL/SQL language features, see "What's New in PL/SQL?".
Tip:
Before tuning older code, benchmark the current system and profile the older subprograms that your program invokes (see "Profiling and Tracing PL/SQL Programs"). With the many automatic optimizations of the PL/SQL optimizer (described in "PL/SQL Optimizer"), you might see performance improvements before doing any tuning.Older dynamic SQL statements written with the DBMS_SQL
package.
If you know at compile time the number and data types of the input and output variables of a dynamic SQL statement, then you can rewrite the statement in native dynamic SQL, which runs noticeably faster than equivalent code that uses the DBMS_SQL
package (especially when it can be optimized by the compiler). For more information, see Chapter 7, "PL/SQL Dynamic SQL."
Code that spends much time processing SQL statements.
Functions invoked in queries, which might run millions of times.
Code that spends much time looping through query results.
See "Tune Loops".
Code that does many numeric computations.
Code that spends much time processing PL/SQL statements (as opposed to issuing database definition language (DDL) statements that PL/SQL passes directly to SQL).
The most common cause of slowness in PL/SQL programs is slow SQL statements. To make SQL statements in a PL/SQL program as efficient as possible:
Use appropriate indexes.
For details, see Oracle Database Performance Tuning Guide.
Use query hints to avoid unnecessary full-table scans.
For details, see Oracle Database Performance Tuning Guide.
Collect current statistics on all tables, using the subprograms in the DBMS_STATS
package.
For details, see Oracle Database Performance Tuning Guide.
Analyze the execution plans and performance of the SQL statements, using:
EXPLAIN
PLAN
statement
For details, see Oracle Database Performance Tuning Guide.
SQL Trace facility with TKPROF
utility
For details, see Oracle Database Performance Tuning Guide.
Use bulk SQL, a set of PL/SQL features that minimizes the performance overhead of the communication between PL/SQL and SQL.
For details, see "Bulk SQL and Bulk Binding".
Functions invoked in queries might run millions of times. Do not invoke a function in a query unnecessarily, and make the invocation as efficient as possible.
Create a function-based index on the table in the query. The CREATE
INDEX
statement (described in Oracle Database SQL Language Reference) might take a while, but the query can run much faster because the function value for each row is cached.
See Also:
"PL/SQL Function Result Cache" for information about caching the results of PL/SQL functionsIf the query passes a column to a function, then the query cannot use user-created indexes on that column, so the query might invoke the function for every row of the table (which might be very large). To minimize the number of function invocations, use a nested query. Have the inner query filter the result set to a small number of rows, and have the outer query invoke the function for only those rows.
In Example 12-5, the two queries produce the same result set, but the second query is more efficient than the first. (In the example, the times and time difference are very small, because the EMPLOYEES
table is very small. For a very large table, they would be significant.)
Example 12-5 Nested Query Improves Performance
DECLARE starting_time TIMESTAMP WITH TIME ZONE; ending_time TIMESTAMP WITH TIME ZONE; BEGIN -- Invokes SQRT for every row of employees table: SELECT SYSTIMESTAMP INTO starting_time FROM DUAL; FOR item IN ( SELECT DISTINCT(SQRT(department_id)) col_alias FROM employees ORDER BY col_alias ) LOOP DBMS_OUTPUT.PUT_LINE('Square root of dept. ID = ' || item.col_alias); END LOOP; SELECT SYSTIMESTAMP INTO ending_time FROM DUAL; DBMS_OUTPUT.PUT_LINE('Time = ' || TO_CHAR(ending_time - starting_time)); -- Invokes SQRT for every distinct department_id of employees table: SELECT SYSTIMESTAMP INTO starting_time FROM DUAL; FOR item IN ( SELECT SQRT(department_id) col_alias FROM (SELECT DISTINCT department_id FROM employees) ORDER BY col_alias ) LOOP IF item.col_alias IS NOT NULL THEN DBMS_OUTPUT.PUT_LINE('Square root of dept. ID = ' || item.col_alias); END IF; END LOOP; SELECT SYSTIMESTAMP INTO ending_time FROM DUAL; DBMS_OUTPUT.PUT_LINE('Time = ' || TO_CHAR(ending_time - starting_time)); END; /
Result:
Square root of dept. ID = 3.16227766016837933199889354443271853372 Square root of dept. ID = 4.47213595499957939281834733746255247088 Square root of dept. ID = 5.47722557505166113456969782800802133953 Square root of dept. ID = 6.32455532033675866399778708886543706744 Square root of dept. ID = 7.07106781186547524400844362104849039285 Square root of dept. ID = 7.74596669241483377035853079956479922167 Square root of dept. ID = 8.36660026534075547978172025785187489393 Square root of dept. ID = 8.94427190999915878563669467492510494176 Square root of dept. ID = 9.48683298050513799599668063329815560116 Square root of dept. ID = 10 Square root of dept. ID = 10.48808848170151546991453513679937598475 Time = +000000000 00:00:00.046000000 Square root of dept. ID = 3.16227766016837933199889354443271853372 Square root of dept. ID = 4.47213595499957939281834733746255247088 Square root of dept. ID = 5.47722557505166113456969782800802133953 Square root of dept. ID = 6.32455532033675866399778708886543706744 Square root of dept. ID = 7.07106781186547524400844362104849039285 Square root of dept. ID = 7.74596669241483377035853079956479922167 Square root of dept. ID = 8.36660026534075547978172025785187489393 Square root of dept. ID = 8.94427190999915878563669467492510494176 Square root of dept. ID = 9.48683298050513799599668063329815560116 Square root of dept. ID = 10 Square root of dept. ID = 10.48808848170151546991453513679937598475 Time = +000000000 00:00:00.000000000
If a subprogram has OUT
or IN
OUT
parameters, you can sometimes decrease its invocation overhead by declaring those parameters with the NOCOPY
hint (described in "NOCOPY").
By default, PL/SQL passes OUT
and IN
OUT
subprogram parameters by value. Before running the subprogram, PL/SQL copies each OUT
and IN
OUT
parameter to a temporary variable, which holds the value of the parameter during subprogram execution. If the subprogram is exited normally, then PL/SQL copies the value of the temporary variable to the corresponding actual parameter. If the subprogram is exited with an unhandled exception, then PL/SQL does not change the value of the actual parameter.
When OUT
or IN
OUT
parameters represent large data structures such as collections, records, and instances of ADTs, copying them slows execution and increases memory use—especially for an instance of an ADT.
For each invocation of an ADT method, PL/SQL copies every attribute of the ADT. If the method is exited normally, then PL/SQL applies any changes that the method made to the attributes. If the method is exited with an unhandled exception, then PL/SQL does not change the attributes.
If your program does not require that an OUT
or IN
OUT
parameter retain its pre-invocation value if the subprogram ends with an unhandled exception, then include the NOCOPY
hint in the parameter declaration. The NOCOPY
hint requests (but does not ensure) that the compiler pass the corresponding actual parameter by reference instead of value. For more information about NOCOPY
, see "NOCOPY". For information about using NOCOPY
with member methods of ADTs, see Oracle Database Object-Relational Developer's Guide.
Caution:
Do not rely onNOCOPY
(which the compiler might or might not obey for a particular invocation) to ensure that an actual parameter or ADT attribute retains its pre-invocation value if the subprogram is exited with an unhandled exception. Instead, ensure that the subprogram handle all exceptions.In Example 12-6, if the compiler obeys the NOCOPY
hint for the invocation of do_nothing2
, then the invocation of do_nothing2
is faster than the invocation of do_nothing1
.
Example 12-6 NOCOPY Subprogram Parameters
DECLARE TYPE EmpTabTyp IS TABLE OF employees%ROWTYPE; emp_tab EmpTabTyp := EmpTabTyp(NULL); -- initialize t1 NUMBER; t2 NUMBER; t3 NUMBER; PROCEDURE get_time (t OUT NUMBER) IS BEGIN t := DBMS_UTILITY.get_time; END; PROCEDURE do_nothing1 (tab IN OUT EmpTabTyp) IS BEGIN NULL; END; PROCEDURE do_nothing2 (tab IN OUT NOCOPY EmpTabTyp) IS BEGIN NULL; END; BEGIN SELECT * INTO emp_tab(1) FROM employees WHERE employee_id = 100; emp_tab.EXTEND(49999, 1); -- Copy element 1 into 2..50000 get_time(t1); do_nothing1(emp_tab); -- Pass IN OUT parameter get_time(t2); do_nothing2(emp_tab); -- Pass IN OUT NOCOPY parameter get_time(t3); DBMS_OUTPUT.PUT_LINE ('Call Duration (secs)'); DBMS_OUTPUT.PUT_LINE ('--------------------'); DBMS_OUTPUT.PUT_LINE ('Just IN OUT: ' || TO_CHAR((t2 - t1)/100.0)); DBMS_OUTPUT.PUT_LINE ('With NOCOPY: ' || TO_CHAR((t3 - t2))/100.0); END; /
Because PL/SQL applications are often built around loops, it is important to optimize both the loops themselves and the code inside them.
If you must loop through a result set more than once, or issue other queries as you loop through a result set, you might be able to change the original query to give you exactly the results you want. Explore the SQL set operators that let you combine multiple queries, described in Oracle Database SQL Language Reference.
You can also use subqueries to do the filtering and sorting in multiple stages—see "Query Result Set Processing with Subqueries".
See Also:
"Bulk SQL and Bulk Binding"These recommendations apply especially (but not only) to computation-intensive PL/SQL code.
Avoid using data types in the NUMBER
data type family (described in "NUMBER Data Type Family"). These data types are represented internally in a format designed for portability and arbitrary scale and precision, not for performance. Operations on data of these types use library arithmetic, while operations on data of the types PLS_INTEGER
, BINARY_FLOAT
and BINARY_DOUBLE
use hardware arithmetic.
For local integer variables, use PLS_INTEGER
, described in "PLS_INTEGER and BINARY_INTEGER Data Types". For variables that can never have the value NULL
, do not need overflow checking, and are not used in performance-critical code, use SIMPLE_INTEGER
, described in "SIMPLE_INTEGER Subtype of PLS_INTEGER".
For floating-point variables, use BINARY_FLOAT
or BINARY_DOUBLE
, described in Oracle Database SQL Language Reference. For variables that can never have the value NULL
and are not used in performance-critical code, use SIMPLE_FLOAT
or SIMPLE_DOUBLE
, explained in "Additional PL/SQL Subtypes of BINARY_FLOAT and BINARY_DOUBLE".
Note:
BINARY_FLOAT
and BINARY_DOUBLE
and their subtypes are less suitable for financial code where accuracy is critical, because they do not always represent fractional values precisely, and handle rounding differently than the NUMBER
types.Many SQL numeric functions (described in Oracle Database SQL Language Reference) are overloaded with versions that accept BINARY_FLOAT
and BINARY_DOUBLE
parameters. You can speed up computation-intensive code by passing variables of these data types to such functions, and by invoking the conversion functions TO_BINARY_FLOAT
(described in Oracle Database SQL Language Reference) and TO_BINARY_DOUBLE
(described in Oracle Database SQL Language Reference) when passing expressions to such functions.
In performance-critical code, avoid constrained subtypes (described in "Constrained Subtypes"). Each assignment to a variable or parameter of a constrained subtype requires extra checking at run time to ensure that the value to be assigned does not violate the constraint.
See Also:
Appendix E, "PL/SQL Predefined Data Types" includes predefined constrained subtypesAt run time, PL/SQL converts between different data types implicitly (automatically) if necessary. For example, if you assign a PLS_INTEGER
variable to a NUMBER
variable, then PL/SQL converts the PLS_INTEGER
value to a NUMBER
value (because the internal representations of the values differ).
Whenever possible, minimize implicit conversions. For example:
If a variable is to be either inserted into a table column or assigned a value from a table column, then give the variable the same data type as the table column.
Make each literal the same data type as the variable to which it is assigned or the expression in which it appears.
Convert values from SQL data types to PL/SQL data types and then use the converted values in expressions.
For example, convert NUMBER
values to PLS_INTEGER
values and then use the PLS_INTEGER
values in expressions. PLS_INTEGER
operations use hardware arithmetic, so they are faster than NUMBER
operations, which use library arithmetic. For more information about the PLS_INTEGER
data type, see "PLS_INTEGER and BINARY_INTEGER Data Types".
Before assigning a value of one SQL data type to a variable of another SQL data type, explicitly convert the source value to the target data type, using a SQL conversion function (for information about SQL conversion functions, see Oracle Database SQL Language Reference).
Overload your subprograms with versions that accept parameters of different data types and optimize each version for its parameter types. For information about overloaded subprograms, see "Overloaded Subprograms".
See Also:
Oracle Database SQL Language Reference for information about implicit conversion of SQL data types (which are also PL/SQL data types)
SQL has many highly optimized character functions, which use low-level code that is more efficient than PL/SQL code. Use these functions instead of writing PL/SQL code to do the same things.
See:
Oracle Database SQL Language Reference for information about SQL character functions that return character values
Oracle Database SQL Language Reference for information about SQL character functions that return NLS character values
Oracle Database SQL Language Reference for information about SQL character functions that return number values
Example 6-6 for an example of PL/SQL code that uses SQL character function REGEXP_LIKE
PL/SQL stops evaluating a logical expression as soon as it can determine the result. Take advantage of this short-circuit evaluation by putting the conditions that are least expensive to evaluate first in logical expressions whenever possible. For example, test the values of PL/SQL variables before testing function return values, so that if the variable tests fail, PL/SQL need not invoke the functions:
IF boolean_variable OR (number > 10) OR boolean_function(parameter) THEN ...
See Also:
"Short-Circuit Evaluation"Bulk SQL minimizes the performance overhead of the communication between PL/SQL and SQL.
PL/SQL and SQL communicate as follows: To run a SELECT
INTO
or DML statement, the PL/SQL engine sends the query or DML statement to the SQL engine. The SQL engine runs the query or DML statement and returns the result to the PL/SQL engine.
The PL/SQL features that comprise bulk SQL are the FORALL
statement and the BULK
COLLECT
clause. The FORALL
statement sends DML statements from PL/SQL to SQL in batches rather than one at a time. The BULK
COLLECT
clause returns results from SQL to PL/SQL in batches rather than one at a time. If a query or DML statement affects four or more database rows, then bulk SQL can significantly improve performance.
Note:
You cannot perform bulk SQL on remote tables.Assigning values to PL/SQL variables that appear in SQL statements is called binding. PL/SQL binding operations fall into these categories:
For in-binds and out-binds, bulk SQL uses bulk binding; that is, it binds an entire collection of values at once. For a collection of n elements, bulk SQL uses a single operation to perform the equivalent of n SELECT
INTO
or DML statements. A query that uses bulk SQL can return any number of rows, without using a FETCH
statement for each one.
The FORALL
statement, a feature of bulk SQL, sends DML statements from PL/SQL to SQL in batches rather than one at a time. To understand the FORALL
statement, first consider the FOR
LOOP
statement in Example 12-7. It sends these DML statements from PL/SQL to SQL one at a time:
DELETE FROM employees_temp WHERE department_id = depts(10); DELETE FROM employees_temp WHERE department_id = depts(30); DELETE FROM employees_temp WHERE department_id = depts(70);
Example 12-7 DELETE Statement in FOR LOOP Statement
DROP TABLE employees_temp; CREATE TABLE employees_temp AS SELECT * FROM employees; DECLARE TYPE NumList IS VARRAY(20) OF NUMBER; depts NumList := NumList(10, 30, 70); -- department numbers BEGIN FOR i IN depts.FIRST..depts.LAST LOOP DELETE FROM employees_temp WHERE department_id = depts(i); END LOOP; END; /
Now consider the FORALL
statement in Example 12-8. It sends the same three DML statements from PL/SQL to SQL as a batch.
Example 12-8 DELETE Statement in FORALL Statement
DROP TABLE employees_temp; CREATE TABLE employees_temp AS SELECT * FROM employees; DECLARE TYPE NumList IS VARRAY(20) OF NUMBER; depts NumList := NumList(10, 30, 70); -- department numbers BEGIN FORALL i IN depts.FIRST..depts.LAST DELETE FROM employees_temp WHERE department_id = depts(i); END; /
A FORALL
statement is usually much faster than an equivalent FOR
LOOP
statement. However, a FOR
LOOP
statement can contain multiple DML statements, while a FORALL
statement can contain only one. The batch of DML statements that a FORALL
statement sends to SQL differ only in their VALUES
and WHERE
clauses. The values in those clauses must come from existing, populated collections.
Note:
The DML statement in aFORALL
statement can reference multiple collections, but performance benefits apply only to collection references that use the FORALL
index variable as an index.Example 12-9 inserts the same collection elements into two database tables, using a FOR
LOOP
statement for the first table and a FORALL
statement for the second table and showing how long each statement takes. (Times vary from run to run.)
Example 12-9 Time Difference for INSERT Statement in FOR LOOP and FORALL Statements
DROP TABLE parts1; CREATE TABLE parts1 ( pnum INTEGER, pname VARCHAR2(15) ); DROP TABLE parts2; CREATE TABLE parts2 ( pnum INTEGER, pname VARCHAR2(15) ); DECLARE TYPE NumTab IS TABLE OF parts1.pnum%TYPE INDEX BY PLS_INTEGER; TYPE NameTab IS TABLE OF parts1.pname%TYPE INDEX BY PLS_INTEGER; pnums NumTab; pnames NameTab; iterations CONSTANT PLS_INTEGER := 50000; t1 INTEGER; t2 INTEGER; t3 INTEGER; BEGIN FOR j IN 1..iterations LOOP -- populate collections pnums(j) := j; pnames(j) := 'Part No. ' || TO_CHAR(j); END LOOP; t1 := DBMS_UTILITY.get_time; FOR i IN 1..iterations LOOP INSERT INTO parts1 (pnum, pname) VALUES (pnums(i), pnames(i)); END LOOP; t2 := DBMS_UTILITY.get_time; FORALL i IN 1..iterations INSERT INTO parts2 (pnum, pname) VALUES (pnums(i), pnames(i)); t3 := DBMS_UTILITY.get_time; DBMS_OUTPUT.PUT_LINE('Execution Time (secs)'); DBMS_OUTPUT.PUT_LINE('---------------------'); DBMS_OUTPUT.PUT_LINE('FOR LOOP: ' || TO_CHAR((t2 - t1)/100)); DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)/100)); COMMIT; END; /
Result is similar to:
Execution Time (secs) --------------------- FOR LOOP: 2.16 FORALL: .11 PL/SQL procedure successfully completed.
In Example 12-10, the FORALL
statement applies to a subset of a collection.
Example 12-10 FORALL Statement for Subset of Collection
DROP TABLE employees_temp;
CREATE TABLE employees_temp AS SELECT * FROM employees;
DECLARE
TYPE NumList IS VARRAY(10) OF NUMBER;
depts NumList := NumList(5,10,20,30,50,55,57,60,70,75);
BEGIN
FORALL j IN 4..7
DELETE FROM employees_temp WHERE department_id = depts(j);
END;
/
See Also:
"FORALL Statement" for its complete syntax and semantics, including restrictions
"Implicit Cursors" for information about implicit cursor attributes in general and other implicit cursor attributes that you can use with the FORALL
statement
If the FORALL
statement bounds clause references a sparse collection, then specify only existing index values, using either the INDICES
OF
or VALUES
OF
clause. You can use INDICES
OF
for any collection except an associative array indexed by string. You can use VALUES
OF
only for a collection of PLS_INTEGER
elements indexed by PLS_INTEGER
.
A collection of PLS_INTEGER
elements indexed by PLS_INTEGER
can be an index collection; that is, a collection of pointers to elements of another collection (the indexed collection).
Index collections are useful for processing different subsets of the same collection with different FORALL
statements. Instead of copying elements of the original collection into new collections that represent the subsets (which can use significant time and memory), represent each subset with an index collection and then use each index collection in the VALUES
OF
clause of a different FORALL
statement.
Example 12-11 uses a FORALL
statement with the INDICES
OF
clause to populate a table with the elements of a sparse collection. Then it uses two FORALL
statements with VALUES
OF
clauses to populate two tables with subsets of a collection.
Example 12-11 FORALL Statements for Sparse Collection and Its Subsets
DROP TABLE valid_orders; CREATE TABLE valid_orders ( cust_name VARCHAR2(32), amount NUMBER(10,2) ); DROP TABLE big_orders; CREATE TABLE big_orders AS SELECT * FROM valid_orders WHERE 1 = 0; DROP TABLE rejected_orders; CREATE TABLE rejected_orders AS SELECT * FROM valid_orders WHERE 1 = 0; DECLARE SUBTYPE cust_name IS valid_orders.cust_name%TYPE; TYPE cust_typ IS TABLE OF cust_name; cust_tab cust_typ; -- Collection of customer names SUBTYPE order_amount IS valid_orders.amount%TYPE; TYPE amount_typ IS TABLE OF NUMBER; amount_tab amount_typ; -- Collection of order amounts TYPE index_pointer_t IS TABLE OF PLS_INTEGER; /* Collections for pointers to elements of cust_tab collection (to represent two subsets of cust_tab): */ big_order_tab index_pointer_t := index_pointer_t(); rejected_order_tab index_pointer_t := index_pointer_t(); PROCEDURE populate_data_collections IS BEGIN cust_tab := cust_typ( 'Company1','Company2','Company3','Company4','Company5' ); amount_tab := amount_typ(5000.01, 0, 150.25, 4000.00, NULL); END; BEGIN populate_data_collections; DBMS_OUTPUT.PUT_LINE ('--- Original order data ---'); FOR i IN 1..cust_tab.LAST LOOP DBMS_OUTPUT.PUT_LINE ( 'Customer #' || i || ', ' || cust_tab(i) || ': $' || amount_tab(i) ); END LOOP; -- Delete invalid orders: FOR i IN 1..cust_tab.LAST LOOP IF amount_tab(i) IS NULL OR amount_tab(i) = 0 THEN cust_tab.delete(i); amount_tab.delete(i); END IF; END LOOP; -- cust_tab is now a sparse collection. DBMS_OUTPUT.PUT_LINE ('--- Order data with invalid orders deleted ---'); FOR i IN 1..cust_tab.LAST LOOP IF cust_tab.EXISTS(i) THEN DBMS_OUTPUT.PUT_LINE ( 'Customer #' || i || ', ' || cust_tab(i) || ': $' || amount_tab(i) ); END IF; END LOOP; -- Using sparse collection, populate valid_orders table: FORALL i IN INDICES OF cust_tab INSERT INTO valid_orders (cust_name, amount) VALUES (cust_tab(i), amount_tab(i)); populate_data_collections; -- Restore original order data -- cust_tab is a dense collection again. /* Populate collections of pointers to elements of cust_tab collection (which represent two subsets of cust_tab): */ FOR i IN cust_tab.FIRST .. cust_tab.LAST LOOP IF amount_tab(i) IS NULL OR amount_tab(i) = 0 THEN rejected_order_tab.EXTEND; rejected_order_tab(rejected_order_tab.LAST) := i; END IF; IF amount_tab(i) > 2000 THEN big_order_tab.EXTEND; big_order_tab(big_order_tab.LAST) := i; END IF; END LOOP; /* Using each subset in a different FORALL statement, populate rejected_orders and big_orders tables: */ FORALL i IN VALUES OF rejected_order_tab INSERT INTO rejected_orders (cust_name, amount) VALUES (cust_tab(i), amount_tab(i)); FORALL i IN VALUES OF big_order_tab INSERT INTO big_orders (cust_name, amount) VALUES (cust_tab(i), amount_tab(i)); END; /
Result:
--- Original order data --- Customer #1, Company1: $5000.01 Customer #2, Company2: $0 Customer #3, Company3: $150.25 Customer #4, Company4: $4000 Customer #5, Company5: $ --- Data with invalid orders deleted --- Customer #1, Company1: $5000.01 Customer #3, Company3: $150.25 Customer #4, Company4: $4000
Verify that correct order details were stored:
SELECT cust_name "Customer", amount "Valid order amount" FROM valid_orders ORDER BY cust_name;
Result:
Customer Valid order amount -------------------------------- ------------------ Company1 5000.01 Company3 150.25 Company4 4000 3 rows selected.
Query:
SELECT cust_name "Customer", amount "Big order amount" FROM big_orders ORDER BY cust_name;
Result:
Customer Big order amount -------------------------------- ---------------- Company1 5000.01 Company4 4000 2 rows selected.
Query:
SELECT cust_name "Customer", amount "Rejected order amount" FROM rejected_orders ORDER BY cust_name;
Result:
Customer Rejected order amount -------------------------------- --------------------- Company2 0 Company5 2 rows selected.
In a FORALL
statement without the SAVE
EXCEPTIONS
clause, if one DML statement raises an unhandled exception, then PL/SQL stops the FORALL
statement and rolls back all changes made by previous DML statements.
For example, the FORALL
statement in Example 12-8 executes these DML statements in this order, unless one of them raises an unhandled exception:
DELETE FROM employees_temp WHERE department_id = depts(10); DELETE FROM employees_temp WHERE department_id = depts(30); DELETE FROM employees_temp WHERE department_id = depts(70);
If the third statement raises an unhandled exception, then PL/SQL rolls back the changes that the first and second statements made. If the second statement raises an unhandled exception, then PL/SQL rolls back the changes that the first statement made and never runs the third statement.
You can handle exceptions raised in a FORALL
statement in either of these ways:
As each exception is raised (see "Handling FORALL Exceptions Immediately")
After the FORALL
statement completes execution, by including the SAVE
EXCEPTIONS
clause (see "Handling FORALL Exceptions After FORALL Statement Completes")
To handle exceptions raised in a FORALL
statement immediately, omit the SAVE
EXCEPTIONS
clause and write the appropriate exception handlers. (For information about exception handlers, see Chapter 11, "PL/SQL Error Handling.") If one DML statement raises a handled exception, then PL/SQL rolls back the changes made by that statement, but does not roll back changes made by previous DML statements.
In Example 12-12, the FORALL
statement is designed to run three UPDATE
statements. However, the second one raises an exception. An exception handler handles the exception, displaying the error message and committing the change made by the first UPDATE
statement. The third UPDATE
statement never runs.
Example 12-12 Handling FORALL Exceptions Immediately
DROP TABLE emp_temp; CREATE TABLE emp_temp ( deptno NUMBER(2), job VARCHAR2(18) ); CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS TYPE NumList IS TABLE OF NUMBER; depts NumList := NumList(10, 20, 30); error_message VARCHAR2(100); BEGIN -- Populate table: INSERT INTO emp_temp (deptno, job) VALUES (10, 'Clerk'); INSERT INTO emp_temp (deptno, job) VALUES (20, 'Bookkeeper'); INSERT INTO emp_temp (deptno, job) VALUES (30, 'Analyst'); COMMIT; -- Append 9-character string to each job: FORALL j IN depts.FIRST..depts.LAST UPDATE emp_temp SET job = job || ' (Senior)' WHERE deptno = depts(j); EXCEPTION WHEN OTHERS THEN error_message := SQLERRM; DBMS_OUTPUT.PUT_LINE (error_message); COMMIT; -- Commit results of successful updates RAISE; END; /
Result:
Procedure created.
Invoke procedure:
BEGIN p; END; /
Result:
ORA-12899: value too large for column "HR"."EMP_TEMP"."JOB" (actual: 19, maximum: 18) ORA-06512: at "HR.P", line 27 ORA-06512: at line 2 PL/SQL procedure successfully completed.
Query:
SELECT * FROM emp_temp;
Result:
DEPTNO JOB
---------- ------------------
10 Clerk (Senior)
20 Bookkeeper
30 Analyst
3 rows selected.
To allow a FORALL
statement to continue even if some of its DML statements fail, include the SAVE
EXCEPTIONS
clause. When a DML statement fails, PL/SQL does not raise an exception; instead, it saves information about the failure. After the FORALL
statement completes, PL/SQL raises a single exception for the FORALL
statement (ORA-24381). In the exception handler for ORA-24381, you can get information about each individual DML statement failure from the implicit cursor attribute SQL%BULK_EXCEPTIONS
.
SQL%BULK_EXCEPTIONS
is like an associative array of information about the DML statements that failed during the most recently run FORALL
statement.
SQL%BULK_EXCEPTIONS
.COUNT
is the number of DML statements that failed. If SQL%BULK_EXCEPTIONS
.COUNT
is not zero, then for each index value i from 1 through SQL%BULK_EXCEPTIONS
.COUNT
:
SQL%BULK_EXCEPTIONS(
i
)
.ERROR_INDEX
is the number of the DML statement that failed.
SQL%BULK_EXCEPTIONS(
i
)
.ERROR_CODE
is the Oracle Database error code for the failure.
For example, if a FORALL
SAVE
EXCEPTIONS
statement runs 100 DML statements, and the tenth and sixty-fourth ones fail with error codes ORA-12899 and ORA-19278, respectively, then:
SQL%BULK_EXCEPTIONS
.COUNT
= 2
SQL%BULK_EXCEPTIONS(1)
.ERROR_INDEX
= 10
SQL%BULK_EXCEPTIONS(1)
.ERROR_CODE
= 12899
SQL%BULK_EXCEPTIONS(2)
.ERROR_INDEX
= 64
SQL%BULK_EXCEPTIONS(2)
.ERROR_CODE
= 19278
Note:
After aFORALL
statement without the SAVE
EXCEPTIONS
clause raises an exception, SQL%BULK_EXCEPTIONS
.COUNT
= 1.With the error code, you can get the associated error message with the SQLERRM
function (described in "SQLERRM Function"):
SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE))
However, the error message that SQLERRM
returns excludes any substitution arguments (compare the error messages in Example 12-12 and Example 12-13).
Example 12-13 is like Example 12-12 except:
The FORALL
statement includes the SAVE
EXCEPTIONS
clause.
The exception-handling part has an exception handler for ORA-24381, the internally defined exception that PL/SQL raises implicitly when a bulk operation raises and saves exceptions. The example gives ORA-24381 the user-defined name dml_errors
.
The exception handler for dml_errors
uses SQL%BULK_EXCEPTIONS
and SQLERRM
(and some local variables) to show the error message and which statement, collection item, and string caused the error.
Example 12-13 Handling FORALL Exceptions After FORALL Statement Completes
CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS TYPE NumList IS TABLE OF NUMBER; depts NumList := NumList(10, 20, 30); error_message VARCHAR2(100); bad_stmt_no PLS_INTEGER; bad_deptno emp_temp.deptno%TYPE; bad_job emp_temp.job%TYPE; dml_errors EXCEPTION; PRAGMA EXCEPTION_INIT(dml_errors, -24381); BEGIN -- Populate table: INSERT INTO emp_temp (deptno, job) VALUES (10, 'Clerk'); INSERT INTO emp_temp (deptno, job) VALUES (20, 'Bookkeeper'); INSERT INTO emp_temp (deptno, job) VALUES (30, 'Analyst'); COMMIT; -- Append 9-character string to each job: FORALL j IN depts.FIRST..depts.LAST SAVE EXCEPTIONS UPDATE emp_temp SET job = job || ' (Senior)' WHERE deptno = depts(j); EXCEPTION WHEN dml_errors THEN FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP error_message := SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); DBMS_OUTPUT.PUT_LINE (error_message); bad_stmt_no := SQL%BULK_EXCEPTIONS(i).ERROR_INDEX; DBMS_OUTPUT.PUT_LINE('Bad statement #: ' || bad_stmt_no); bad_deptno := depts(bad_stmt_no); DBMS_OUTPUT.PUT_LINE('Bad department #: ' || bad_deptno); SELECT job INTO bad_job FROM emp_temp WHERE deptno = bad_deptno; DBMS_OUTPUT.PUT_LINE('Bad job: ' || bad_job); END LOOP; COMMIT; -- Commit results of successful updates WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Unrecognized error.'); RAISE; END; /
Result:
Procedure created.
Invoke procedure:
BEGIN p; END; /
Result:
ORA-12899: value too large for column (actual: , maximum: ) Bad statement #: 2 Bad department #: 20 Bad job: Bookkeeper PL/SQL procedure successfully completed.
Query:
SELECT * FROM emp_temp;
Result:
DEPTNO JOB
---------- ------------------
10 Clerk (Senior)
20 Bookkeeper
30 Analyst (Senior)
3 rows selected.
If the FORALL
statement bounds clause references a sparse collection, then to find the collection element that caused a DML statement to fail, you must step through the elements one by one until you find the element whose index is SQL%BULK_EXCEPTIONS(i)
.ERROR_INDEX
. Then, if the FORALL
statement uses the VALUES
OF
clause to reference a collection of pointers into another collection, you must find the element of the other collection whose index is SQL%BULK_EXCEPTIONS(i)
.ERROR_INDEX
.
After a FORALL
statement completes, you can get the number of rows that each DML statement affected from the implicit cursor attribute SQL%BULK_ROWCOUNT
. (To get the total number of rows affected by the FORALL
statement, use the implicit cursor attribute SQL%ROWCOUNT
, described in "SQL%ROWCOUNT Attribute: How Many Rows Were Affected?".)
SQL%BULK_ROWCOUNT
is like an associative array whose ith element is the number of rows affected by the ith DML statement in the most recently completed FORALL
statement. The data type of the element is PLS_INTEGER
.
Note:
If the number of rows exceeds the maximum value for aPLS_INTEGER
, then the element has a negative value. For information about PLS_INTEGER
, see "PLS_INTEGER and BINARY_INTEGER Data Types".Example 12-14 uses SQL%BULK_ROWCOUNT
to show how many rows each DELETE
statement in the FORALL
statement deleted and SQL%ROWCOUNT
to show the total number of rows deleted.
Example 12-14 Showing Number of Rows Affected by Each DELETE in FORALL
DROP TABLE emp_temp; CREATE TABLE emp_temp AS SELECT * FROM employees; DECLARE TYPE NumList IS TABLE OF NUMBER; depts NumList := NumList(30, 50, 60); BEGIN FORALL j IN depts.FIRST..depts.LAST DELETE FROM emp_temp WHERE department_id = depts(j); FOR i IN depts.FIRST..depts.LAST LOOP DBMS_OUTPUT.PUT_LINE ( 'Statement #' || i || ' deleted ' || SQL%BULK_ROWCOUNT(i) || ' rows.' ); END LOOP; DBMS_OUTPUT.PUT_LINE('Total rows deleted: ' || SQL%ROWCOUNT); END; /
Result:
Statement #1 deleted 6 rows. Statement #2 deleted 45 rows. Statement #3 deleted 5 rows. Total rows deleted: 56
Example 12-15 uses SQL%BULK_ROWCOUNT
to show how many rows each INSERT
SELECT
construct in the FORALL
statement inserted and SQL%ROWCOUNT
to show the total number of rows inserted.
Example 12-15 Showing Number of Rows Affected by Each INSERT SELECT in FORALL
DROP TABLE emp_by_dept; CREATE TABLE emp_by_dept AS SELECT employee_id, department_id FROM employees WHERE 1 = 0; DECLARE TYPE dept_tab IS TABLE OF departments.department_id%TYPE; deptnums dept_tab; BEGIN SELECT department_id BULK COLLECT INTO deptnums FROM departments; FORALL i IN 1..deptnums.COUNT INSERT INTO emp_by_dept (employee_id, department_id) SELECT employee_id, department_id FROM employees WHERE department_id = deptnums(i) ORDER BY department_id, employee_id; FOR i IN 1..deptnums.COUNT LOOP -- Count how many rows were inserted for each department; that is, -- how many employees are in each department. DBMS_OUTPUT.PUT_LINE ( 'Dept '||deptnums(i)||': inserted '|| SQL%BULK_ROWCOUNT(i)||' records' ); END LOOP; DBMS_OUTPUT.PUT_LINE('Total records inserted: ' || SQL%ROWCOUNT); END; /
Result:
Dept 10: inserted 1 records Dept 20: inserted 2 records Dept 30: inserted 6 records Dept 40: inserted 1 records Dept 50: inserted 45 records Dept 60: inserted 5 records Dept 70: inserted 1 records Dept 80: inserted 34 records Dept 90: inserted 3 records Dept 100: inserted 6 records Dept 110: inserted 2 records Dept 120: inserted 0 records Dept 130: inserted 0 records Dept 140: inserted 0 records Dept 150: inserted 0 records Dept 160: inserted 0 records Dept 170: inserted 0 records Dept 180: inserted 0 records Dept 190: inserted 0 records Dept 200: inserted 0 records Dept 210: inserted 0 records Dept 220: inserted 0 records Dept 230: inserted 0 records Dept 240: inserted 0 records Dept 250: inserted 0 records Dept 260: inserted 0 records Dept 270: inserted 0 records Dept 280: inserted 0 records Total records inserted: 106
The BULK
COLLECT
clause, a feature of bulk SQL, returns results from SQL to PL/SQL in batches rather than one at a time. The BULK
COLLECT
clause can appear in:
SELECT
INTO
statement
FETCH
statement
RETURNING
INTO
clause of:
DELETE
statement
INSERT
statement
UPDATE
statement
EXECUTE
IMMEDIATE
statement
With the BULK
COLLECT
clause, each of the preceding statements retrieves an entire result set and stores it in one or more collection variables in a single operation (which is more efficient than using a loop statement to retrieve one result row at a time).
Note:
PL/SQL processes theBULK
COLLECT
clause similar to the way it processes a FETCH
statement inside a LOOP
statement. PL/SQL does not raise an exception when a statement with a BULK
COLLECT
clause returns no rows. You must check the target collections for emptiness, as in Example 12-22.The SELECT
INTO
statement with the BULK
COLLECT
clause (also called the SELECT
BULK
COLLECT
INTO
statement) selects an entire result set into one or more collection variables. For more information, see "SELECT INTO Statement".
Caution:
TheSELECT
BULK
COLLECT
INTO
statement is vulnerable to aliasing, which can cause unexpected results. For details, see "SELECT BULK COLLECT INTO Statements and Aliasing".Example 12-16 uses a SELECT
BULK
COLLECT
INTO
statement to select two database columns into two collections (nested tables).
Example 12-16 Bulk-Selecting Two Database Columns into Two Nested Tables
DECLARE TYPE NumTab IS TABLE OF employees.employee_id%TYPE; TYPE NameTab IS TABLE OF employees.last_name%TYPE; enums NumTab; names NameTab; PROCEDURE print_first_n (n POSITIVE) IS BEGIN IF enums.COUNT = 0 THEN DBMS_OUTPUT.PUT_LINE ('Collections are empty.'); ELSE DBMS_OUTPUT.PUT_LINE ('First ' || n || ' employees:'); FOR i IN 1 .. n LOOP DBMS_OUTPUT.PUT_LINE ( ' Employee #' || enums(i) || ': ' || names(i)); END LOOP; END IF; END; BEGIN SELECT employee_id, last_name BULK COLLECT INTO enums, names FROM employees ORDER BY employee_id; print_first_n(3); print_first_n(6); END; /
Result:
First 3 employees: Employee #100: King Employee #101: Kochhar Employee #102: De Haan First 6 employees: Employee #100: King Employee #101: Kochhar Employee #102: De Haan Employee #103: Hunold Employee #104: Ernst Employee #105: Austin
Example 12-17 uses a SELECT
BULK
COLLECT
INTO
statement to select a result set into a nested table of records.
Example 12-17 Bulk-Selecting into Nested Table of Records
DECLARE CURSOR c1 IS SELECT first_name, last_name, hire_date FROM employees; TYPE NameSet IS TABLE OF c1%ROWTYPE; stock_managers NameSet; -- nested table of records BEGIN -- Assign values to nested table of records: SELECT first_name, last_name, hire_date BULK COLLECT INTO stock_managers FROM employees WHERE job_id = 'ST_MAN' ORDER BY hire_date; -- Print nested table of records: FOR i IN stock_managers.FIRST .. stock_managers.LAST LOOP DBMS_OUTPUT.PUT_LINE ( stock_managers(i).hire_date || ' ' || stock_managers(i).last_name || ', ' || stock_managers(i).first_name ); END LOOP;END; /
Result:
01-MAY-03 Kaufling, Payam 18-JUL-04 Weiss, Matthew 10-APR-05 Fripp, Adam 10-OCT-05 Vollman, Shanta 16-NOV-07 Mourgos, Kevin
In a statement of the form
SELECT column BULK COLLECT INTO collection FROM table ...
column
and collection
are analogous to IN
NOCOPY
and OUT
NOCOPY
subprogram parameters, respectively, and PL/SQL passes them by reference. As with subprogram parameters that are passed by reference, aliasing can cause unexpected results.
In Example 12-18, the intention is to select specific values from a collection, numbers1
, and then store them in the same collection. The unexpected result is that all elements of numbers1
are deleted. For workarounds, see Example 12-19 and Example 12-20.
Example 12-18 SELECT BULK COLLECT INTO Statement with Unexpected Results
CREATE OR REPLACE TYPE numbers_type IS TABLE OF INTEGER / CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS numbers1 numbers_type := numbers_type(1,2,3,4,5); BEGIN DBMS_OUTPUT.PUT_LINE('Before SELECT statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); FOR j IN 1..numbers1.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j)); END LOOP; --Self-selecting BULK COLLECT INTO clause: SELECT a.COLUMN_VALUE BULK COLLECT INTO numbers1 FROM TABLE(numbers1) a WHERE a.COLUMN_VALUE > p.i ORDER BY a.COLUMN_VALUE; DBMS_OUTPUT.PUT_LINE('After SELECT statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); END p; /
Invoke p
:
BEGIN
p(2);
END;
/
Result:
Before SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 After SELECT statement numbers1.COUNT() = 0 PL/SQL procedure successfully completed.
Invoke p
:
BEGIN
p(10);
END;
/
Result:
Before SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 After SELECT statement numbers1.COUNT() = 0
Example 12-19 uses a cursor to achieve the result intended by Example 12-18.
Example 12-19 Cursor Workaround for Example 12-18
CREATE OR REPLACE TYPE numbers_type IS TABLE OF INTEGER / CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS numbers1 numbers_type := numbers_type(1,2,3,4,5); CURSOR c IS SELECT a.COLUMN_VALUE FROM TABLE(numbers1) a WHERE a.COLUMN_VALUE > p.i ORDER BY a.COLUMN_VALUE; BEGIN DBMS_OUTPUT.PUT_LINE('Before FETCH statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); FOR j IN 1..numbers1.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j)); END LOOP; OPEN c; FETCH c BULK COLLECT INTO numbers1; CLOSE c; DBMS_OUTPUT.PUT_LINE('After FETCH statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); IF numbers1.COUNT() > 0 THEN FOR j IN 1..numbers1.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j)); END LOOP; END IF; END p; /
Invoke p
:
BEGIN
p(2);
END;
/
Result:
Before FETCH statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 After FETCH statement numbers1.COUNT() = 3 numbers1(1) = 3 numbers1(2) = 4 numbers1(3) = 5
Invoke p
:
BEGIN
p(10);
END;
/
Result:
Before FETCH statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 After FETCH statement numbers1.COUNT() = 0
Example 12-20 selects specific values from a collection, numbers1
, and then stores them in a different collection, numbers2
. Example 12-20 runs faster than Example 12-19.
Example 12-20 Second Collection Workaround for Example 12-18
CREATE OR REPLACE TYPE numbers_type IS TABLE OF INTEGER / CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS numbers1 numbers_type := numbers_type(1,2,3,4,5); numbers2 numbers_type := numbers_type(0,0,0,0,0); BEGIN DBMS_OUTPUT.PUT_LINE('Before SELECT statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); FOR j IN 1..numbers1.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j)); END LOOP; DBMS_OUTPUT.PUT_LINE('numbers2.COUNT() = ' || numbers2.COUNT()); FOR j IN 1..numbers2.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers2(' || j || ') = ' || numbers2(j)); END LOOP; SELECT a.COLUMN_VALUE BULK COLLECT INTO numbers2 -- numbers2 appears here FROM TABLE(numbers1) a -- numbers1 appears here WHERE a.COLUMN_VALUE > p.i ORDER BY a.COLUMN_VALUE; DBMS_OUTPUT.PUT_LINE('After SELECT statement'); DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT()); IF numbers1.COUNT() > 0 THEN FOR j IN 1..numbers1.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j)); END LOOP; END IF; DBMS_OUTPUT.PUT_LINE('numbers2.COUNT() = ' || numbers2.COUNT()); IF numbers2.COUNT() > 0 THEN FOR j IN 1..numbers2.COUNT() LOOP DBMS_OUTPUT.PUT_LINE('numbers2(' || j || ') = ' || numbers2(j)); END LOOP; END IF; END p; /
Invoke p
:
BEGIN
p(2);
END;
/
Result:
Before SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 numbers2.COUNT() = 5 numbers2(1) = 0 numbers2(2) = 0 numbers2(3) = 0 numbers2(4) = 0 numbers2(5) = 0 After SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 numbers2.COUNT() = 3 numbers2(1) = 3 numbers2(2) = 4 numbers2(3) = 5 PL/SQL procedure successfully completed.
Invoke p
:
BEGIN
p(10);
END;
/
Result:
Before SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 numbers2.COUNT() = 5 numbers2(1) = 0 numbers2(2) = 0 numbers2(3) = 0 numbers2(4) = 0 numbers2(5) = 0 After SELECT statement numbers1.COUNT() = 5 numbers1(1) = 1 numbers1(2) = 2 numbers1(3) = 3 numbers1(4) = 4 numbers1(5) = 5 numbers2.COUNT() = 0
A SELECT
BULK
COLLECT
INTO
statement that returns a large number of rows produces a large collection. To limit the number of rows and the collection size, use either the ROWNUM
pseudocolumn (described in Oracle Database SQL Language Reference) or SAMPLE
clause (described in Oracle Database SQL Language Reference).
In Example 12-21, the first SELECT
BULK
COLLECT
INTO
statement uses ROWNUM
to limit the number of rows to 50, and the second SELECT
BULK
COLLECT
INTO
statement uses SAMPLE
to limit the number of rows to approximately 10% of the total.
When a result set is stored in a collection, it is easy to loop through the rows and refer to different columns. This technique can be very fast, but also very memory-intensive. If you use it often:
To loop once through the result set, use a cursor FOR
LOOP
(see "Query Result Set Processing With Cursor FOR LOOP Statements").
This technique avoids the memory overhead of storing a copy of the result set.
Instead of looping through the result set to search for certain values or filter the results into a smaller set, do the searching or filtering in the query of the SELECT
INTO
statement.
For example, in simple queries, use WHERE
clauses; in queries that compare multiple result sets, use set operators such as INTERSECT
and MINUS
. For information about set operators, see Oracle Database SQL Language Reference.
Instead of looping through the result set and running another query for each result row, use a subquery in the query of the SELECT
INTO
statement (see "Query Result Set Processing with Subqueries").
Instead of looping through the result set and running another DML statement for each result row, use the FORALL
statement (see "FORALL Statement").
The FETCH
statement with the BULK
COLLECT
clause (also called the FETCH
BULK
COLLECT
statement) fetches an entire result set into one or more collection variables. For more information, see "FETCH Statement".
Example 12-22 uses a FETCH
BULK
COLLECT
statement to fetch an entire result set into two collections (nested tables).
Example 12-22 Bulk-Fetching into Two Nested Tables
DECLARE
TYPE NameList IS TABLE OF employees.last_name%TYPE;
TYPE SalList IS TABLE OF employees.salary%TYPE;
CURSOR c1 IS
SELECT last_name, salary
FROM employees
WHERE salary > 10000
ORDER BY last_name;
names NameList;
sals SalList;
TYPE RecList IS TABLE OF c1%ROWTYPE;
recs RecList;
v_limit PLS_INTEGER := 10;
PROCEDURE print_results IS
BEGIN
-- Check if collections are empty:
IF names IS NULL OR names.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('No results!');
ELSE
DBMS_OUTPUT.PUT_LINE('Result: ');
FOR i IN names.FIRST .. names.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(' Employee ' || names(i) || ': $' || sals(i));
END LOOP;
END IF;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE ('--- Processing all results simultaneously ---');
OPEN c1;
FETCH c1 BULK COLLECT INTO names, sals;
CLOSE c1;
print_results();
DBMS_OUTPUT.PUT_LINE ('--- Processing ' || v_limit || ' rows at a time ---');
OPEN c1;
LOOP
FETCH c1 BULK COLLECT INTO names, sals LIMIT v_limit;
EXIT WHEN names.COUNT = 0;
print_results();
END LOOP;
CLOSE c1;
DBMS_OUTPUT.PUT_LINE ('--- Fetching records rather than columns ---');
OPEN c1;
FETCH c1 BULK COLLECT INTO recs;
FOR i IN recs.FIRST .. recs.LAST
LOOP
-- Now all columns from result set come from one record
DBMS_OUTPUT.PUT_LINE (
' Employee ' || recs(i).last_name || ': $' || recs(i).salary
);
END LOOP;
END;
/
Result:
--- Processing all results simultaneously --- Result: Employee Abel: $11000 Employee Cambrault: $11000 Employee De Haan: $17000 Employee Errazuriz: $12000 Employee Fripp: $18540.29 Employee Greenberg: $12008 Employee Hartstein: $13000 Employee Higgins: $12008 Employee Kaufling: $17862 Employee King: $24000 Employee Kochhar: $17000 Employee Mourgos: $13113.87 Employee Ozer: $11500 Employee Partners: $13500 Employee Raphaely: $11000 Employee Russell: $14000 Employee Vishney: $10500 Employee Vollman: $14696.58 Employee Weiss: $22907.66 Employee Zlotkey: $10500 --- Processing 10 rows at a time --- Result: Employee Abel: $11000 Employee Cambrault: $11000 Employee De Haan: $17000 Employee Errazuriz: $12000 Employee Fripp: $18540.29 Employee Greenberg: $12008 Employee Hartstein: $13000 Employee Higgins: $12008 Employee Kaufling: $17862 Employee King: $24000 Result: Employee Kochhar: $17000 Employee Mourgos: $13113.87 Employee Ozer: $11500 Employee Partners: $13500 Employee Raphaely: $11000 Employee Russell: $14000 Employee Vishney: $10500 Employee Vollman: $14696.58 Employee Weiss: $22907.66 Employee Zlotkey: $10500 --- Fetching records rather than columns --- Employee Abel: $11000 Employee Cambrault: $11000 Employee De Haan: $17000 Employee Errazuriz: $12000 Employee Fripp: $18540.29 Employee Greenberg: $12008 Employee Hartstein: $13000 Employee Higgins: $12008 Employee Kaufling: $17862 Employee King: $24000 Employee Kochhar: $17000 Employee Mourgos: $13113.87 Employee Ozer: $11500 Employee Partners: $13500 Employee Raphaely: $11000 Employee Russell: $14000 Employee Vishney: $10500 Employee Vollman: $14696.58 Employee Weiss: $22907.66 Employee Zlotkey: $10500
Example 12-23 uses a FETCH
BULK
COLLECT
statement to fetch a result set into a collection (nested table) of records.
Example 12-23 Bulk-Fetching into Nested Table of Records
DECLARE CURSOR c1 IS SELECT first_name, last_name, hire_date FROM employees; TYPE NameSet IS TABLE OF c1%ROWTYPE; stock_managers NameSet; -- nested table of records TYPE cursor_var_type is REF CURSOR; cv cursor_var_type; BEGIN -- Assign values to nested table of records: OPEN cv FOR SELECT first_name, last_name, hire_date FROM employees WHERE job_id = 'ST_MAN' ORDER BY hire_date; FETCH cv BULK COLLECT INTO stock_managers; CLOSE cv; -- Print nested table of records: FOR i IN stock_managers.FIRST .. stock_managers.LAST LOOP DBMS_OUTPUT.PUT_LINE ( stock_managers(i).hire_date || ' ' || stock_managers(i).last_name || ', ' || stock_managers(i).first_name ); END LOOP;END; /
Result:
01-MAY-03 Kaufling, Payam 18-JUL-04 Weiss, Matthew 10-APR-05 Fripp, Adam 10-OCT-05 Vollman, Shanta 16-NOV-07 Mourgos, Kevin
A FETCH
BULK
COLLECT
statement that returns a large number of rows produces a large collection. To limit the number of rows and the collection size, use the LIMIT
clause.
In Example 12-24, with each iteration of the LOOP
statement, the FETCH
statement fetches ten rows (or fewer) into associative array empids
(overwriting the previous values).
Example 12-24 Limiting Bulk FETCH with LIMIT
DECLARE TYPE numtab IS TABLE OF NUMBER INDEX BY PLS_INTEGER; CURSOR c1 IS SELECT employee_id FROM employees WHERE department_id = 80 ORDER BY employee_id; empids numtab; BEGIN OPEN c1; LOOP -- Fetch 10 rows or fewer in each iteration FETCH c1 BULK COLLECT INTO empids LIMIT 10; DBMS_OUTPUT.PUT_LINE ('------- Results from One Bulk Fetch --------'); FOR i IN 1..empids.COUNT LOOP DBMS_OUTPUT.PUT_LINE ('Employee Id: ' || empids(i)); END LOOP; EXIT WHEN c1%NOTFOUND; END LOOP; CLOSE c1; END; /
Result:
------- Results from One Bulk Fetch -------- Employee Id: 145 Employee Id: 146 Employee Id: 147 Employee Id: 148 Employee Id: 149 Employee Id: 150 Employee Id: 151 Employee Id: 152 Employee Id: 153 Employee Id: 154 ------- Results from One Bulk Fetch -------- Employee Id: 155 Employee Id: 156 Employee Id: 157 Employee Id: 158 Employee Id: 159 Employee Id: 160 Employee Id: 161 Employee Id: 162 Employee Id: 163 Employee Id: 164 ------- Results from One Bulk Fetch -------- Employee Id: 165 Employee Id: 166 Employee Id: 167 Employee Id: 168 Employee Id: 169 Employee Id: 170 Employee Id: 171 Employee Id: 172 Employee Id: 173 Employee Id: 174 ------- Results from One Bulk Fetch -------- Employee Id: 175 Employee Id: 176 Employee Id: 177 Employee Id: 179
The RETURNING
INTO
clause with the BULK
COLLECT
clause (also called the RETURNING
BULK
COLLECT
INTO
clause) can appear in an INSERT
, UPDATE
, DELETE
, or EXECUTE
IMMEDIATE
statement. With the RETURNING
BULK
COLLECT
INTO
clause, the statement stores its result set in one or more collections. For more information, see "RETURNING INTO Clause".
Example 12-25 uses a DELETE
statement with the RETURNING
BULK
COLLECT
INTO
clause to delete rows from a table and return them in two collections (nested tables).
Example 12-25 Returning Deleted Rows in Two Nested Tables
DROP TABLE emp_temp; CREATE TABLE emp_temp AS SELECT * FROM employees ORDER BY employee_id; DECLARE TYPE NumList IS TABLE OF employees.employee_id%TYPE; enums NumList; TYPE NameList IS TABLE OF employees.last_name%TYPE; names NameList; BEGIN DELETE FROM emp_temp WHERE department_id = 30 RETURNING employee_id, last_name BULK COLLECT INTO enums, names; DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:'); FOR i IN enums.FIRST .. enums.LAST LOOP DBMS_OUTPUT.PUT_LINE ('Employee #' || enums(i) || ': ' || names(i)); END LOOP; END; /
Result:
Deleted 6 rows: Employee #114: Raphaely Employee #115: Khoo Employee #116: Baida Employee #117: Tobias Employee #118: Himuro Employee #119: Colmenares
In a FORALL
statement, the DML statement can have a RETURNING
BULK
COLLECT
INTO
clause. For each iteration of the FORALL
statement, the DML statement stores the specified values in the specified collections—without overwriting the previous values, as the same DML statement would do in a FOR
LOOP
statement.
In Example 12-26, the FORALL
statement runs a DELETE
statement that has a RETURNING
BULK
COLLECT
INTO
clause. For each iteration of the FORALL
statement, the DELETE
statement stores the employee_id
and department_id
values of the deleted row in the collections e_ids
and d_ids
, respectively.
Example 12-26 DELETE with RETURN BULK COLLECT INTO in FORALL Statement
DROP TABLE emp_temp; CREATE TABLE emp_temp AS SELECT * FROM employees ORDER BY employee_id, department_id; DECLARE TYPE NumList IS TABLE OF NUMBER; depts NumList := NumList(10,20,30); TYPE enum_t IS TABLE OF employees.employee_id%TYPE; e_ids enum_t; TYPE dept_t IS TABLE OF employees.department_id%TYPE; d_ids dept_t; BEGIN FORALL j IN depts.FIRST..depts.LAST DELETE FROM emp_temp WHERE department_id = depts(j) RETURNING employee_id, department_id BULK COLLECT INTO e_ids, d_ids; DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:'); FOR i IN e_ids.FIRST .. e_ids.LAST LOOP DBMS_OUTPUT.PUT_LINE ( 'Employee #' || e_ids(i) || ' from dept #' || d_ids(i) ); END LOOP; END; /
Result:
Deleted 9 rows: Employee #200 from dept #10 Employee #201 from dept #20 Employee #202 from dept #20 Employee #114 from dept #30 Employee #115 from dept #30 Employee #116 from dept #30 Employee #117 from dept #30 Employee #118 from dept #30 Employee #119 from dept #30
Example 12-27 is like Example 12-26 except that it uses a FOR
LOOP
statement instead of a FORALL
statement.
Example 12-27 DELETE with RETURN BULK COLLECT INTO in FOR LOOP Statement
DECLARE TYPE NumList IS TABLE OF NUMBER; depts NumList := NumList(10,20,30); TYPE enum_t IS TABLE OF employees.employee_id%TYPE; e_ids enum_t; TYPE dept_t IS TABLE OF employees.department_id%TYPE; d_ids dept_t; BEGIN FOR j IN depts.FIRST..depts.LAST LOOP DELETE FROM emp_temp WHERE department_id = depts(j) RETURNING employee_id, department_id BULK COLLECT INTO e_ids, d_ids; END LOOP; DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:'); FOR i IN e_ids.FIRST .. e_ids.LAST LOOP DBMS_OUTPUT.PUT_LINE ( 'Employee #' || e_ids(i) || ' from dept #' || d_ids(i) ); END LOOP; END; /
Result:
Deleted 6 rows: Employee #114 from dept #30 Employee #115 from dept #30 Employee #116 from dept #30 Employee #117 from dept #30 Employee #118 from dept #30 Employee #119 from dept #30
Client programs (such as OCI and Pro*C programs) can use PL/SQL anonymous blocks to bulk-bind input and output host arrays. This is the most efficient way to pass collections to and from the database server.
In the client program, declare and assign values to the host variables to be referenced in the anonymous block. In the anonymous block, prefix each host variable name with a colon (:) to distinguish it from a PL/SQL collection variable name. When the client program runs, the database server runs the PL/SQL anonymous block.
In Example 12-28, the anonymous block uses a FORALL
statement to bulk-bind a host input array. In the FORALL
statement, the DELETE
statement refers to four host variables: scalars lower
, upper
, and emp_id
and array depts
.
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.A table function is a user-defined PL/SQL function that returns a collection of rows (a 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. For example:
SELECT * FROM TABLE(table_function_name(parameter_list))
(For more information about the TABLE
clause of the SELECT
statement, see Oracle Database SQL Language Reference.)
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. For more information, see "Chaining Pipelined Table Functions".
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. For information about Oracle Streams, see Oracle Streams Concepts and Administration.
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 Data Cartridge Developer's Guide for information about using pipelined and parallel table functionsA pipelined table function must be either a standalone function or a package function.
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.
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".
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
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
and BOOLEAN
). 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.
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.
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 12-29 creates a package that includes a pipelined table function, f1
, and then selects from the collection of rows that f1
returns.
Example 12-29 Creating and Invoking Pipelined Table Function
CREATE OR REPLACE PACKAGE pkg1 AS TYPE numset_t IS TABLE OF NUMBER; FUNCTION f1(x NUMBER) RETURN numset_t PIPELINED; END pkg1; / CREATE PACKAGE BODY pkg1 AS -- FUNCTION f1 returns a collection of elements (1,2,3,... x) 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.
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 12-30, 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 12-30 Pipelined Table Function Transforms Each Row to Two Rows
CREATE OR REPLACE PACKAGE refcur_pkg 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 Hunold 103 AHUNOLD 590.423.4567 104 Bruce Ernst 104 BERNST 590.423.4568 105 David Austin 105 DAUSTIN 590.423.4569 106 Valli Pataballa 106 VPATABAL 590.423.4560 107 Diana Lorentz 107 DLORENTZ 590.423.5567 10 rows selected.
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).
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 12-31, 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 12-31, 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
.
Example 12-31 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)))))); /
As Example 12-31 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 SQLSELECT
statement passes a CURSOR
expression to a function, the referenced cursor opens when the function begins to run and closes when the function completes.Example 12-32 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 12-32 Pipelined Table Function with Two Cursor Variable Parameters
CREATE OR REPLACE PACKAGE refcur_pkg 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 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 Hunold 60 IT 1400 104 Bruce Ernst 60 IT 1400 105 David Austin 60 IT 1400 106 Valli Pataballa 60 IT 1400 107 Diana Lorentz 60 IT 1400 10 rows selected.
Example 12-33 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 12-33 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 PACKAGE pkg_gpa IS TYPE gpa IS TABLE OF NUMBER; FUNCTION weighted_average(input_values SYS_REFCURSOR) RETURN gpa PIPELINED; END pkg_gpa; / CREATE 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; / SELECT w.column_value "weighted result" FROM TABLE ( pkg_gpa.weighted_average ( CURSOR (SELECT weight, grade FROM gradereport) ) ) w;
Result:
weighted result --------------- 3.5 1 row selected.
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".
You must understand the predefined exception NO_DATA_NEEDED
in two cases:
You include an OTHERS
exception handler in a block that includes a PIPE
ROW
statement
Your code that feeds a PIPE
ROW
statement must be followed by a clean-up procedure
Typically, 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 12-34, 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 12-35.
In Example 12-34, 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.
Example 12-34 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 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.
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, 'Fatal error.'); END;
In Example 12-35, assume that the package External_Source
contains these public items:
Procedure Init
, which allocates and initializes the resources that Next_Row
needs
Function Next_Row
, which returns some data from a specific external source and raises the user-defined exception Done
(which is also a public item in the package) when the external source has no more data
Procedure Clean_Up
, which releases the resources that Init
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 exception External_Source.Done
.
get_external_source_data
needs no more rows.
In this case, the PIPE
ROW
statement in get_external_source_data
raises the NO_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 12-35 Pipelined Table Function Handles NO_DATA_NEEDED
CREATE OR REPLACE FUNCTION get_external_source_data RETURN t AUTHID DEFINER PIPELINED 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; /
The DBMS_PARALLEL_EXECUTE
package enables you to incrementally update the data in a large table in parallel, in two high-level steps:
Group sets of rows in the table into smaller chunks.
Apply the desired UPDATE
statement to the chunks in parallel, committing each time you have finished processing a chunk.
This technique is recommended whenever you are updating a lot of data. Its advantages are:
You lock only one set of rows at a time, for a relatively short time, instead of locking the entire table.
You do not lose work that has been done if something fails before the entire operation finishes.
You reduce rollback space consumption.
You improve performance.
See Also:
Oracle Database PL/SQL Packages and Types Reference for more information about theDBMS_PARALLEL_EXECUTE
packagePL/Scope extracts, organizes, and stores data about user-defined identifiers from PL/SQL source text. You can retrieve source text identifier data with the static data dictionary views *_IDENTIFIERS
. For more information, see Oracle Database Advanced Application Developer's Guide.
To help you isolate performance problems in large PL/SQL programs, PL/SQL provides these tools, implemented as PL/SQL packages:
Tool | Package | Description |
---|---|---|
Profiler API | DBMS_PROFILER |
Computes the time that your PL/SQL program spends at each line and in each subprogram.
You must have Saves runtime statistics in database tables, which you can query. |
Trace API | DBMS_TRACE |
Traces the order in which subprograms run.
You can specify the subprograms to trace and the tracing level. Saves runtime statistics in database tables, which you can query. |
PL/SQL hierarchical profiler | DBMS_HPROF |
Reports the dynamic execution program profile of your PL/SQL program, organized by subprogram invocations. Accounts for SQL and PL/SQL execution times separately.
Requires no special source or compile-time preparation. Generates reports in HTML. Provides the option of storing results in relational format in database tables for custom report generation (such as third-party tools offer). |
For a detailed description of PL/SQL hierarchical profiler, see Oracle Database Advanced Application Developer's Guide.
The Profiler API ("Profiler") is implemented as PL/SQL package DBMS_PROFILER
, whose services compute the time that your PL/SQL program spends at each line and in each subprogram and save these statistics in database tables, which you can query.
Note:
You can use Profiler only on units for which you have CREATE privilege. You do not need the CREATE privilege to use the PL/SQL hierarchical profiler (see Oracle Database Advanced Application Developer's Guide).To use Profiler:
Start the profiling session.
Run your PL/SQL program long enough to get adequate code coverage.
Flush the collected data to the database.
Stop the profiling session.
After you have collected data with Profiler, you can:
Query the database tables that contain the performance data.
Identify the subprograms and packages that use the most execution time.
Determine why your program spent more time accessing certain data structures and running certain code segments.
Inspect possible performance bottlenecks such as SQL statements, loops, and recursive functions.
Use the results of your analysis to replace inappropriate data structures and rework slow algorithms.
For example, with an exponential growth in data, you might need to replace a linear search with a binary search.
For detailed information about the DBMS_PROFILER
subprograms, see Oracle Database PL/SQL Packages and Types Reference.
The Trace API ("Trace") is implemented as PL/SQL package DBMS_TRACE
, whose services trace execution by subprogram or exception and save these statistics in database tables, which you can query.
To use Trace:
(Optional) Limit tracing to specific subprograms and choose a tracing level.
Tracing all subprograms and exceptions in a large program can produce huge amounts of data that are difficult to manage.
Start the tracing session.
Run your PL/SQL program.
Stop the tracing session.
After you have collected data with Trace, you can query the database tables that contain the performance data and analyze it in the same way that you analyze the performance data from Profiler (see "Profiler API: Package DBMS_PROFILER").
For detailed information about the DBMS_TRACE
subprograms, see Oracle Database PL/SQL Packages and Types Reference.
You can usually speed up PL/SQL units by compiling them into native code (processor-dependent system code), which is stored in the SYSTEM tablespace.
You can natively compile any PL/SQL unit of any type, including those that Oracle Database supplies.
Natively compiled program units work in all server environments, including shared server configuration (formerly called "multithreaded server") and Oracle Real Application Clusters (Oracle RAC).
On most platforms, PL/SQL native compilation requires no special set-up or maintenance. On some platforms, the DBA might want to do some optional configuration.
See Also:
Oracle Database Administrator's Guide for information about configuring a database
Platform-specific configuration documentation for your platform
You can test to see how much performance gain you can get by enabling PL/SQL native compilation.
If you have determined that PL/SQL native compilation will provide significant performance gains in database operations, Oracle recommends compiling the entire database for native mode, which requires DBA privileges. This speeds up both your own code and calls to the PL/SQL packages that Oracle Database supplies.
* Requires DBA privileges.
Whether to compile a PL/SQL unit for native or interpreted mode depends on where you are in the development cycle and on what the program unit does.
While you are debugging program units and recompiling them frequently, interpreted mode has these advantages:
You can use PL/SQL debugging tools on program units compiled for interpreted mode (but not for those compiled for native mode).
Compiling for interpreted mode is faster than compiling for native mode.
After the debugging phase of development, in determining whether to compile a PL/SQL unit for native mode, consider:
PL/SQL native compilation provides the greatest performance gains for computation-intensive procedural operations. Examples are data warehouse applications and applications with extensive server-side transformations of data for display.
PL/SQL native compilation provides the least performance gains for PL/SQL subprograms that spend most of their time running SQL.
When many program units (typically over 15,000) are compiled for native execution, and are simultaneously active, the large amount of shared memory required might affect system performance.
Without native compilation, the PL/SQL statements in a PL/SQL unit are compiled into an intermediate form, system code, which is stored in the catalog and interpreted at run time.
With PL/SQL native compilation, the PL/SQL statements in a PL/SQL unit are compiled into native code and stored in the catalog. The native code need not be interpreted at run time, so it runs faster.
Because native compilation applies only to PL/SQL statements, a PL/SQL unit that uses only SQL statements might not run faster when natively compiled, but it does run at least as fast as the corresponding interpreted code. The compiled code and the interpreted code make the same library calls, so their action is the same.
The first time a natively compiled PL/SQL unit runs, it is fetched from the SYSTEM tablespace into shared memory. Regardless of how many sessions invoke the program unit, shared memory has only one copy it. If a program unit is not being used, the shared memory it is using might be freed, to reduce memory load.
Natively compiled subprograms and interpreted subprograms can invoke each other.
PL/SQL native compilation works transparently in an Oracle Real Application Clusters (Oracle RAC) environment.
The PLSQL_CODE_TYPE
compilation parameter determines whether PL/SQL code is natively compiled or interpreted. For information about this compilation parameters, see "PL/SQL Units and Compilation Parameters".
Recompilation is automatic with invalidated PL/SQL modules. For example, if an object on which a natively compiled PL/SQL subprogram depends changes, the subprogram is invalidated. The next time the same subprogram is called, the database recompiles the subprogram automatically. Because the PLSQL_CODE_TYPE
setting is stored inside the library unit for each subprogram, the automatic recompilation uses this stored setting for code type.
Explicit recompilation does not necessarily use the stored PLSQL_CODE_TYPE
setting. For the conditions under which explicit recompilation uses stored settings, see "PL/SQL Units and Compilation Parameters".
If you have DBA privileges, you can set up a new database for PL/SQL native compilation by setting the compilation parameter PLSQL_CODE_TYPE
to NATIVE
. The performance benefits apply to the PL/SQL packages that Oracle Database supplies, which are used for many database operations.
Note:
If you compile the whole database asNATIVE
, Oracle recommends that you set PLSQL_CODE_TYPE
at the system level.If you have DBA privileges, you can recompile all PL/SQL modules in an existing database to NATIVE
or INTERPRETED
, using the dbmsupgnv
.sql
and dbmsupgin
.sql
scripts respectively during the process explained in this section. Before making the conversion, review "Determining Whether to Use PL/SQL Native Compilation".
Note:
If you compile the whole database asNATIVE
, Oracle recommends that you set PLSQL_CODE_TYPE
at the system level.During the conversion to native compilation, TYPE
specifications are not recompiled by dbmsupgnv
.sql
to NATIVE
because these specifications do not contain executable code.
Package specifications seldom contain executable code so the runtime benefits of compiling to NATIVE
are not measurable. You can use the TRUE
command-line parameter with the dbmsupgnv
.sql
script to exclude package specs from recompilation to NATIVE
, saving time in the conversion process.
When converting to interpreted compilation, the dbmsupgin
.sql
script does not accept any parameters and does not exclude any PL/SQL units.
Note:
The following procedure describes the conversion to native compilation. If you must recompile all PL/SQL modules to interpreted compilation, make these changes in the steps.Skip the first step.
Set the PLSQL_CODE_TYPE
compilation parameter to INTERPRETED
rather than NATIVE
.
Substitute dbmsupgin
.sql
for the dbmsupgnv
.sql
script.
Ensure that a test PL/SQL unit can be compiled. For example:
ALTER PROCEDURE my_proc COMPILE PLSQL_CODE_TYPE=NATIVE REUSE SETTINGS;
Shut down application services, the listener, and the database.
Shut down all of the Application services including the Forms Processes, Web Servers, Reports Servers, and Concurrent Manager Servers. After shutting down all of the Application services, ensure that all of the connections to the database were terminated.
Shut down the TNS listener of the database to ensure that no new connections are made.
Shut down the database in normal or immediate mode as the user SYS
. See Oracle Database Administrator's Guide.
Set PLSQL_CODE_TYPE
to NATIVE
in the compilation parameter file. If the database is using a server parameter file, then set this after the database has started.
The value of PLSQL_CODE_TYPE
does not affect the conversion of the PL/SQL units in these steps. However, it does affect all subsequently compiled units, so explicitly set it to the desired compilation type.
Start up the database in upgrade mode, using the UPGRADE
option. For information about SQL*Plus STARTUP
, see SQL*Plus User's Guide and Reference.
Run this code to list the invalid PL/SQL units. You can save the output of the query for future reference with the SQL SPOOL
statement:
-- To save the output of the query to a file: SPOOL pre_update_invalid.log SELECT o.OWNER, o.OBJECT_NAME, o.OBJECT_TYPE FROM DBA_OBJECTS o, DBA_PLSQL_OBJECT_SETTINGS s WHERE o.OBJECT_NAME = s.NAME AND o.STATUS='INVALID'; -- To stop spooling the output: SPOOL OFF
If any Oracle supplied units are invalid, try to validate them by recompiling them. For example:
ALTER PACKAGE SYS.DBMS_OUTPUT COMPILE BODY REUSE SETTINGS;
If the units cannot be validated, save the spooled log for future resolution and continue.
Run this query to determine how many objects are compiled NATIVE
and INTERPRETED
(to save the output, use the SQL SPOOL
statement):
SELECT TYPE, PLSQL_CODE_TYPE, COUNT(*) FROM DBA_PLSQL_OBJECT_SETTINGS WHERE PLSQL_CODE_TYPE IS NOT NULL GROUP BY TYPE, PLSQL_CODE_TYPE ORDER BY TYPE, PLSQL_CODE_TYPE;
Any objects with a NULL
plsql_code_type
are special internal objects and can be ignored.
Run the $ORACLE_HOME/rdbms/admin/dbmsupgnv
.sql
script as the user SYS
to update the plsql_code_type
setting to NATIVE
in the dictionary tables for all PL/SQL units. This process also invalidates the units. Use TRUE
with the script to exclude package specifications; FALSE
to include the package specifications.
This update must be done when the database is in UPGRADE
mode. The script is guaranteed to complete successfully or rollback all the changes.
Shut down the database and restart in NORMAL
mode.
Before you run the utlrp
.sql
script, Oracle recommends that no other sessions are connected to avoid possible problems. You can ensure this with this statement:
ALTER SYSTEM ENABLE RESTRICTED SESSION;
Run the $ORACLE_HOME/rdbms/admin/utlrp
.sql
script as the user SYS
. This script recompiles all the PL/SQL modules using a default degree of parallelism. See the comments in the script for information about setting the degree explicitly.
If for any reason the script is abnormally terminated, rerun the utlrp
.sql
script to recompile any remaining invalid PL/SQL modules.
After the compilation completes successfully, verify that there are no invalid PL/SQL units using the query in step 5. You can spool the output of the query to the post_upgrade_invalid
.log
file and compare the contents with the pre_upgrade_invalid
.log
file, if it was created previously.
Reexecute the query in step 6. If recompiling with dbmsupgnv
.sql
, confirm that all PL/SQL units, except TYPE
specifications and package specifications if excluded, are NATIVE
. If recompiling with dbmsupgin
.sql
, confirm that all PL/SQL units are INTERPRETED
.
Disable the restricted session mode for the database, then start the services that you previously shut down. To disable restricted session mode, use this statement:
ALTER SYSTEM DISABLE RESTRICTED SESSION;