1
PL/SQL Collections A composite data type stores values that have internal components. You can pass entire
composite variables to subprograms as parameters, and you can access internal components
of composite variables individually. Internal components can be either scalar or composite.
You can use scalar components wherever you can use scalar variables. PL/SQL lets you
define two kinds of composite data types, collection and record. You can use composite
components wherever you can use composite variables of the same type.
In a collection, the internal components always have the same data type, and are
called elements. You can access each element of a collection variable by its unique index,
with this syntax: variable_name(index). To create a collection variable, you either define a
collection type and then create a variable of that type or use %TYPE.
PL/SQL has three collection types—associative array, VARRAY (variable-size array), and nested
table.
Associative array An associative array (formerly called PL/SQL table or index-by table) is a set of key-
value pairs. Each key is a unique index, used to locate the associated value with the
syntax variable_name(index).
The data type of index can be either a string type or PLS_INTEGER. Indexes are stored in sort
order, not creation order. For string types, sort order is determined by the initialization
parameters NLS_SORT and NLS_COMP.
Like a database table, an associative array:
Is empty (but not null) until you populate it
Can hold an unspecified number of elements, which you can access without
knowing their positions
Unlike a database table, an associative array:
Does not need disk space or network operations
Cannot be manipulated with DML statements
2
Example 5-1 Associative Array Indexed by String
DECLARE
-- Associative array indexed by string:
TYPE population IS TABLE OF NUMBER -- Associative array type
INDEX BY VARCHAR2(64); -- indexed by string
city_population population; -- Associative array variable
i VARCHAR2(64); -- Scalar variable
BEGIN
-- Add elements (key-value pairs) to associative array:
city_population('Smallville') := 2000;
city_population('Midland') := 750000;
city_population('Megalopolis') := 1000000;
-- Change value associated with key 'Smallville':
city_population('Smallville') := 2001;
-- Print associative array:
i := city_population.FIRST; -- Get first element of array
WHILE i IS NOT NULL LOOP
DBMS_Output.PUT_LINE
('Population of ' || i || ' is ' || city_population(i));
i := city_population.NEXT(i); -- Get next element of array
END LOOP;
END;
/
Result:
Population of Megalopolis is 1000000
Population of Midland is 750000
Population of Smallville is 2001
3
Example 5-2 Function Returns Associative Array Indexed by PLS_INTEGER
DECLARE
TYPE sum_multiples IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
n PLS_INTEGER := 5; -- number of multiples to sum for display
sn PLS_INTEGER := 10; -- number of multiples to sum
m PLS_INTEGER := 3; -- multiple
FUNCTION get_sum_multiples (
multiple IN PLS_INTEGER,
num IN PLS_INTEGER
) RETURN sum_multiples
IS
s sum_multiples;
BEGIN
FOR i IN 1..num LOOP
s(i) := multiple * ((i * (i + 1)) / 2); -- sum of multiples
END LOOP;
RETURN s;
END get_sum_multiples;
BEGIN
DBMS_OUTPUT.PUT_LINE (
'Sum of the first ' || TO_CHAR(n) || ' multiples of ' ||
TO_CHAR(m) || ' is ' || TO_CHAR(get_sum_multiples (m, sn)(n))
);
END;
/
Result:
Sum of the first 5 multiples of 3 is 45
4
Example 5-3 Declaring Associative Array Constant
CREATE OR REPLACE PACKAGE My_Types AUTHID DEFINER IS
TYPE My_AA IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
FUNCTION Init_My_AA RETURN My_AA;
END My_Types;
/
CREATE OR REPLACE PACKAGE BODY My_Types IS
FUNCTION Init_My_AA RETURN My_AA IS
Ret My_AA;
BEGIN
Ret(-10) := '-ten';
Ret(0) := 'zero';
Ret(1) := 'one';
Ret(2) := 'two';
Ret(3) := 'three';
Ret(4) := 'four';
Ret(9) := 'nine';
RETURN Ret;
END Init_My_AA;
END My_Types;
/
DECLARE
v CONSTANT My_Types.My_AA := My_Types.Init_My_AA();
BEGIN
DECLARE
Idx PLS_INTEGER := v.FIRST();
BEGIN
WHILE Idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(TO_CHAR(Idx, '999')||LPAD(v(Idx), 7));
Idx := v.NEXT(Idx);
END LOOP;
END;
END;
/
Result:
-10 -ten
0 zero
1 one
2 two
3 three
4 four
9 nine
PL/SQL procedure successfully completed.
5
Appropriate Uses for Associative Arrays
An associative array is appropriate for:
A relatively small lookup table, which can be constructed in memory each time
you invoke the subprogram or initialize the package that declares it
Passing collections to and from the database server
An associative array is intended for temporary data storage. To make an
associative array persistent for the life of a database session, declare it in a package specification and populate it in the package body.
Note:
You cannot declare an associative array type at schema level. Therefore, to pass an
associative array variable as a parameter to a standalone subprogram, you must
declare the type of that variable in a package specification. Doing so makes the type
available to both the invoked subprogram (which declares a formal parameter of that
type) and the invoking subprogram or anonymous block (which declares and passes
the variable of that type).
Example 10-2 Passing Associative Array to Standalone Subprogram
CREATE OR REPLACE PACKAGE aa_pkg IS
TYPE aa_type IS TABLE OF INTEGER INDEX BY VARCHAR2(15);
END;
/
CREATE OR REPLACE PROCEDURE print_aa (
aa aa_pkg.aa_type
) IS
i VARCHAR2(15);
BEGIN
i := aa.FIRST;
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE (aa(i) || ' ' || i);
i := aa.NEXT(i);
END LOOP;
END;
/
DECLARE
aa_var aa_pkg.aa_type;
BEGIN
aa_var('zero') := 0;
aa_var('one') := 1;
aa_var('two') := 2;
print_aa(aa_var);
END;
/
Result:
1 one
2 two
0 zero
6
Example 5-8 Data Type Compatibility for Collection Assignment DECLARE
TYPE triplet IS TABLE OF VARCHAR2(15) INDEX BY PLS_INTEGER;
TYPE trio IS TABLE OF VARCHAR2(15) INDEX BY PLS_INTEGER;
group1 triplet;
group2 triplet;
group3 trio;
BEGIN
group1(1) := 'Eve';
group2 := group1; -- succeeds
group3 := group1; -- fails
END;
/
Result:
Error report:
ORA-06550: 12 line, 13 column:
PLS-00382: expression is of wrong type
Example 5-13 Nested Tables of Associative Arrays and Varrays of Strings
DECLARE
TYPE tb1 IS TABLE OF INTEGER INDEX BY PLS_INTEGER; -- associative arrays
v4 tb1;
TYPE aa1 IS TABLE OF tb1 INDEX BY PLS_INTEGER; -- associative array of
v2 aa1; -- associative arrays
BEGIN
v4(1) := 34; -- populate associative array
v4(2) := 46456;
v4(456) := 343;
v2(23) := v4; -- populate associative array of associative arrays
END;
/
7
Collection methods
Method Type Description
DELETE Procedure Deletes elements from collection.
TRIM Procedure Deletes elements from end of varray or nested table.
EXTEND Procedure Adds elements to end of varray or nested table.
EXISTS Function Returns TRUE if and only if specified element of varray or nested
table exists.
FIRST Function Returns first index in collection.
LAST Function Returns last index in collection.
COUNT Function Returns number of elements in collection.
LIMIT Function Returns maximum number of elements that collection can have.
PRIOR Function Returns index that precedes specified index.
NEXT Function Returns index that succeeds specified index.
DELETE Collection Method
DELETE is a procedure that deletes elements from a collection. This method has these forms:
DELETE deletes all elements from a collection of any type.
This operation immediately frees the memory allocated to the deleted
elements.
From an associative array or nested table (but not a varray):
o DELETE(n) deletes the element whose index is n, if that element exists;
otherwise, it does nothing.
o DELETE(m,n) deletes all elements whose indexes are in the range m..n, if
both m and n exist and m <= n; otherwise, it does nothing.
For these two forms of DELETE, PL/SQL keeps placeholders for the deleted
elements. Therefore, the deleted elements are included in the internal size of
the collection, and you can restore a deleted element by assigning a valid value to it.
8
Example 5-18 DELETE Method with Associative Array Indexed by String
CREATE OR REPLACE PROCEDURE print (n INTEGER) IS
BEGIN
IF n IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(n);
ELSE
DBMS_OUTPUT.PUT_LINE('NULL');
END IF;
END print;
/
DECLARE
TYPE aa_type_str IS TABLE OF INTEGER INDEX BY VARCHAR2(10);
aa_str aa_type_str;
PROCEDURE print_aa_str IS
i VARCHAR2(10);
BEGIN
i := aa_str.FIRST;
IF i IS NULL THEN
DBMS_OUTPUT.PUT_LINE('aa_str is empty');
ELSE
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT('aa_str.(' || i || ') = '); print(aa_str(i));
i := aa_str.NEXT(i);
END LOOP;
END IF;
DBMS_OUTPUT.PUT_LINE('---');
END print_aa_str;
BEGIN
aa_str('M') := 13;
aa_str('Z') := 26;
aa_str('C') := 3;
print_aa_str;
aa_str.DELETE; -- Delete all elements
print_aa_str;
aa_str('M') := 13; -- Replace deleted element with same value
aa_str('Z') := 260; -- Replace deleted element with new value
aa_str('C') := 30; -- Replace deleted element with new value
aa_str('W') := 23; -- Add new element
aa_str('J') := 10; -- Add new element
aa_str('N') := 14; -- Add new element
aa_str('P') := 16; -- Add new element
aa_str('W') := 23; -- Add new element
aa_str('J') := 10; -- Add new element
print_aa_str;
aa_str.DELETE('C'); -- Delete one element
print_aa_str;
aa_str.DELETE('N','W'); -- Delete range of elements
print_aa_str;
aa_str.DELETE('Z','M'); -- Does nothing
print_aa_str;
END;
/
9
FIRST and LAST Methods for Associative Array
For an associative array indexed by PLS_INTEGER, the first and last elements are those with the smallest and largest indexes, respectively.
Example 5-22 FIRST and LAST Values for Associative Array Indexed by
PLS_INTEGER
DECLARE
TYPE aa_type_int IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
aa_int aa_type_int;
PROCEDURE print_first_and_last IS
BEGIN
DBMS_OUTPUT.PUT_LINE('FIRST = ' || aa_int.FIRST);
DBMS_OUTPUT.PUT_LINE('LAST = ' || aa_int.LAST);
END print_first_and_last;
BEGIN
aa_int(1) := 3;
aa_int(2) := 6;
aa_int(3) := 9;
aa_int(4) := 12;
DBMS_OUTPUT.PUT_LINE('Before deletions:');
print_first_and_last;
aa_int.DELETE(1);
aa_int.DELETE(4);
DBMS_OUTPUT.PUT_LINE('After deletions:');
print_first_and_last;
END;
/
Result:
Before deletions:
FIRST = 1
LAST = 4
After deletions:
FIRST = 2
LAST = 3
10
Example 5-23 FIRST and LAST Values for Associative Array Indexed by String
DECLARE
TYPE aa_type_str IS TABLE OF INTEGER INDEX BY VARCHAR2(10);
aa_str aa_type_str;
PROCEDURE print_first_and_last IS
BEGIN
DBMS_OUTPUT.PUT_LINE('FIRST = ' || aa_str.FIRST);
DBMS_OUTPUT.PUT_LINE('LAST = ' || aa_str.LAST);
END print_first_and_last;
BEGIN
aa_str('Z') := 26;
aa_str('A') := 1;
aa_str('K') := 11;
aa_str('R') := 18;
DBMS_OUTPUT.PUT_LINE('Before deletions:');
print_first_and_last;
aa_str.DELETE('A');
aa_str.DELETE('Z');
DBMS_OUTPUT.PUT_LINE('After deletions:');
print_first_and_last;
END;
/
Result:
Before deletions:
FIRST = A
LAST = Z
After deletions:
FIRST = K
LAST = R
11
COUNT Collection Method
COUNT is a function that returns the number of elements in the collection (ignoring
deleted elements, even if DELETE kept placeholders for them).
LIMIT Collection Method
LIMIT is a function that returns the maximum number of elements that the collection
can have. If the collection has no maximum number of elements, LIMITreturns NULL. Only a varray has a maximum size.
Example 5-28 LIMIT and COUNT Values for Different Collection Types
DECLARE
TYPE aa_type IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
aa aa_type; -- associative array
TYPE va_type IS VARRAY(4) OF INTEGER;
va va_type := va_type(2,4); -- varray
TYPE nt_type IS TABLE OF INTEGER;
nt nt_type := nt_type(1,3,5); -- nested table
BEGIN
aa(1):=3; aa(2):=6; aa(3):=9; aa(4):= 12;
DBMS_OUTPUT.PUT('aa.COUNT = '); print(aa.COUNT);
DBMS_OUTPUT.PUT('aa.LIMIT = '); print(aa.LIMIT);
DBMS_OUTPUT.PUT('va.COUNT = '); print(va.COUNT);
DBMS_OUTPUT.PUT('va.LIMIT = '); print(va.LIMIT);
DBMS_OUTPUT.PUT('nt.COUNT = '); print(nt.COUNT);
DBMS_OUTPUT.PUT('nt.LIMIT = '); print(nt.LIMIT);
END;
/
Result:
aa.COUNT = 4
aa.LIMIT = NULL
va.COUNT = 2
va.LIMIT = 4
nt.COUNT = 3
nt.LIMIT = NULL
12
PRIOR and NEXT Collection Methods
PRIOR and NEXT are functions that let you move backward and forward in the
collection (ignoring deleted elements, even if DELETE kept placeholders for them). These methods are useful for traversing sparse collections.
Given an index:
PRIOR returns the index of the preceding existing element of the collection, if
one exists. Otherwise, PRIOR returns NULL.
For any collection c, c.PRIOR(c.FIRST) returns NULL.
NEXT returns the index of the succeeding existing element of the collection, if
one exists. Otherwise, NEXT returns NULL.
For any collection c, c.NEXT(c.LAST) returns NULL.
Consider Example 5-1 as an example
13
FORALL statement
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 FORALLstatement, first consider
the FOR LOOP statement in Example 12-7. It sends these DML statements from PL/SQL to
SQL one at a time:
Example 12-7 DELETE Statement in FOR LOOP Statement
DROP TABLE employees_temp;
CREATE TABLE employees_temp AS SELECT * FROM hr.employees;
DECLARE
TYPE NumList IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
depts NumList;
BEGIN
depts(1) := 10; depts(2) := 30; depts(3) := 70; -- department numbers
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 hr.employees;
DECLARE
TYPE NumList IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
depts NumList;
BEGIN
depts(1) := 10; depts(2) := 30; depts(3) := 70; -- department numbers
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
14
a FORALL statement sends to SQL differ only in their VALUES and WHEREclauses. The values
in those clauses must come from existing, populated collections.
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.
15
Handling FORALL Exceptions After FORALL Statement Completes
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
theFORALL 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
16
Example 12-13 Handling FORALL Exceptions After FORALL Statement Completes
CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
TYPE NumList IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
depts NumList;
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
depts(1) := 10; depts(2) := 30; depts(3) := 70; -- department numbers
-- 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.
17
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.
Getting Number of Rows Affected by FORALL Statement
After a FORALL statement completes, you can get the number of rows that each DML
statement affected from the implicit cursor attributeSQL%BULK_ROWCOUNT.
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
completedFORALL statement. The data type of the element is PLS_INTEGER.
Example 12-14 Showing Number of Rows Affected by Each DELETE in FORALL
DROP TABLE emp_temp;
CREATE TABLE emp_temp AS SELECT * FROM hr.employees;
DECLARE
TYPE NumList IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
depts NumList;
BEGIN
depts(1) := 30; depts(2) := 50; depts(3) := 60; -- department numbers
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
18
BULK COLLECT Clause
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:
o DELETE statement
o INSERT statement
o UPDATE statement
o 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).
Example 12-16 Bulk-Selecting Two Database Columns into Two Nested Tables
DECLARE
TYPE NumTab IS TABLE OF hr.employees.employee_id%TYPE INDEX BY PLS_INTEGER;
TYPE NameTab IS TABLE OF hr.employees.last_name%TYPE INDEX BY PLS_INTEGER;
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 hr.employees
ORDER BY employee_id;
print_first_n(3);
print_first_n(6);
END;
/
Result:
First 3 employees:
Employee #100: King
Employee #101: Kochhar
19
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
20
FETCH Statement with BULK COLLECT Clause
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.
DECLARE
TYPE NameList IS TABLE OF hr.employees.last_name%TYPE INDEX BY PLS_INTEGER;
TYPE SalList IS TABLE OF hr.employees.salary%TYPE INDEX BY PLS_INTEGER;
CURSOR c1 IS
SELECT last_name, salary
FROM hr.employees
WHERE salary > 10000
ORDER BY last_name;
names NameList;
sals SalList;
TYPE RecList IS TABLE OF c1%ROWTYPE INDEX BY PLS_INTEGER;
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;
/