Bulk Collect
Bulk Collect
Bulk Collect
With FORALL and BULK COLLECT, however, you can fine-tune the way these two engines
communicate, effectively telling the PL/SQL engine to compress multiple context switches into a
single switch, thereby improving the performance of your applications.
At runtime, the PL/SQL engine takes the “template” UPDATE statement and collection of bind
variable values and generates them into a set of statements, which are then passed to the SQL
engine with a single context switch. In other words, the same SQL statements are executed, but
they are all run in the same roundtrip to the SQL layer, minimizing the context switches
The SQL engine automatically initializes and extends the collections you reference in the
BULK COLLECT clause. It starts filling the collections at index 1, and inserts elements
consecutively (densely).
DECLARE
CURSOR allrows_cur IS SELECT * FROM employees;
TYPE employee_aat IS TABLE OF allrows_cur%ROWTYPE INDEX BY
BINARY_INTEGER;
l_employees employee_aat;
BEGIN
OPEN allrows_cur;
LOOP
FETCH allrows_cur BULK COLLECT INTO l_employees LIMIT 100;
/* Process the data by scanning through the collection. */
FOR l_row IN 1 .. l_employees.COUNT
LOOP
upgrade_employee_status (l_employees(l_row).employee_id);
END LOOP;
CLOSE allrows_cur;
END;
Starting with Oracle Database 12c, you can also use the FIRST ROWS clause to limit the number
of rows fetched with BULK COLLECT. The following block of code uses the LIMIT clause in a
FETCH that is inside a simple loop. This code will retrieve just the first 50 rows identified by the
SELECT statement:
DECLARE
TYPE salaries_t IS TABLE OF employees.salary%TYPE;
l_salaries salaries_t;
BEGIN
SELECT salary BULK COLLECT INTO sals FROM employees
FETCH FIRST 50 ROWS ONLY;
END;
/
Using the RETURNING clause with bulk operations
The RETURNING clause allows you to obtain information (such as a newly updated value for a
salary) from a DML statement. RETURNING can help you avoid additional queries to the database
to determine the results of DML operations that have just completed.
update employee
set salary = salary * 1.1
returning salary bulk collect into l_type;
dbms_output.put_line(l_type(i));
end loop;
delete employee
where dept_id = 20
returning employee_id bulk collect into l_deleted_emp_id;
end;
HIGH-SPEED DML WITH FORALL
FORALL index IN
[ lower_bound ... upper_bound |
INDICES OF indexing_collection |
VALUES OF indexing_collection
]
[ SAVE EXCEPTIONS ]
sql_statement;
Example
DECLARE
TYPE employee_aat IS TABLE OF employees%ROWTYPE
INDEX BY PLS_INTEGER;
l_employees employee_aat;
BEGIN
FORALL l_index IN l_employees.FIRST .. l_employees.LAST
INSERT INTO employee (employee_id, last_name)
VALUES (l_employees (l_index).employee_id
, l_employees (l_index).last_name
);
END;
Use the RETURNING clause in a FORALL statement to retrieve information about each
separate DELETE statement. Notice that the RETURNING clause in FORALL must use BULK
COLLECT INTO (the corresponding “bulk” operation for queries):
DECLARE
TYPE isbn_list IS TABLE OF VARCHAR2(13);
my_books isbn_list
:= isbn_list (
'1-56592-375-8', '0-596-00121-5', '1-56592-849-0',
'1-56592-335-9', '1-56592-674-9', '1-56592-675-7',
'0-596-00180-0', '1-56592-457-6'
);
BEGIN
FORALL book_index IN
my_books.FIRST..my_books.LAST
UPDATE books
SET page_count = page_count / 2
WHERE isbn = my_books (book_index);
The FORALL statement and %BULK_ROWCOUNT use the same subscripts or row numbers
in the collections. For example, if the collection passed to FORALL has data in rows 10
through 200, then the %BULK_ROWCOUNT pseudocollection will also have rows 10
through 200 defined and populated. Any other rows will be undefined.
When the INSERT affects only a single row (when you specify a VALUES list, for example),
a row’s value in %BULK_ROWCOUNT will be equal to 1. For INSERT...SELECT statements,
however, %BULK_ROWCOUNT can be greater than 1
1. The DML statement that raised the exception is rolled back to an implicit savepoint
marked by the PL/SQL engine before execution of the statement. Changes to all rows
already modified by that statement are rolled back.
2. Any previous DML operations in that FORALL statement that have already completed
without error are not rolled back.
3. If you do not take special action (by adding the SAVE EXCEPTIONS clause to FORALL,
discussed next), the entire FORALL statement stops and the remaining statements are not
executed at all.
EXCEPTION
WHEN bulk_errors
THEN
DBMS_OUTPUT.put_line ('Updated ' || SQL%ROWCOUNT || ' rows.');
The ERROR_INDEX field of each pseudocollection’s row returns the row number in the
driving collection of the FORALL statement for which an exception was raised
The ERROR_CODE field of each pseudocollection’s row returns the error number of the
exception that was raised. Note that this value is stored as a positive integer; you will
need to multiply it by −1 before passing it to SQLERRM or displaying the information.
Note that I can call the COUNT method to determine the number of defined rows (errors
raised), but I cannot call other methods, such as FIRST and LAST
l_type(10) := 78;
l_type(15) := 115;
l_type(200) := 122;
end;
I also include an optional BETWEEN clause to restrict which of those index values will be used.
With INDICES OF , the contents of the indexing array are ignored. All that matters are the
positions or row numbers that are defined in the collection.
VALUES OF example
/* File on web: 10g_values_of.sql */
1 DECLARE
2 TYPE employee_aat IS TABLE OF employees.employee_id%TYPE
3 INDEX BY PLS_INTEGER;
4
5 l_employees employee_aat;
6
7 TYPE indices_aat IS TABLE OF PLS_INTEGER
8 INDEX BY PLS_INTEGER;
9
10 l_employee_indices indices_aat;
11 BEGIN
12 l_employees (-77) := 7820;
13 l_employees (13067) := 7799;
14 l_employees (99999999) := 7369;
15 --
16 l_employee_indices (100) := −77;
17 l_employee_indices (200) := 99999999;
18 --
19 FORALL l_index IN VALUES OF l_employee_indices
20 UPDATE employees
21 SET salary = 10000
22 WHERE employee_id = l_employees (l_index);
23 END;