You are on page 1of 5

Page 1 of 5

Overview of Bulk Binds [ID 1066713.1]

Modified 11-JUN-2010 Type BULLETIN Status PUBLISHED

In this Document
Purpose
Scope and Application
Overview of Bulk Binds

Applies to:
PL/SQL - Version: 9.2.0.1 to 11.2.0.1 - Release: 9.2 to 11.2
Information in this document applies to any platform.

Purpose
The purpose of this document is to provide a quick overview of bulk binding and bulk collect. It provides information and examples of bulk operations showing how and when performance
can be improved.

Scope and Application


This article is a quick reference guide for PL/SQL Application developers & Support analysts with a view to understanding the advantages and limitations of Bulk operations.

Overview of Bulk Binds

Oracle Database uses two engines to run PL/SQL blocks and subprograms. The PL/SQL engine runs procedural statements, while the SQL engine runs SQL statements. During
execution, every SQL statement causes a context switch between the two engines, resulting in a performance overhead.

Performance can be improved substantially by minimizing the number of context switches required to run a particular block or subprogram. When a SQL statement runs inside a loop that
uses collection elements as bind variables, the large number of context switches required by the block can cause poor performance.

Collections include the following:

• Varrays
• Nested tables
• Index-by tables
• Host arrays

Binding is the assignment of values to PL/SQL variables in SQL statements. Bulk binding is binding an entire collection at once. Bulk binds pass the entire collection back and forth
between the two engines in a single operation.

Typically, using bulk binds improves performance for SQL statements that affect four or more database rows. The more rows affected by a SQL statement, the greater the performance
gain from bulk binds.

How to Bulk Bind

Bulk binding includes the following:

1. Input collections, use the FORALL statement


2. Output collections, use BULK COLLECT clause

When to Use Bulk Binds with Examples

If you have scenarios like these in your application, consider using bulk binds to improve performance.

• DML Statements that reference collections


• The FORALL keyword can improve the performance of INSERT, UPDATE, or DELETE statements that reference collection elements.

For example, the following PL/SQL block increases the salary for employees whose manager's ID number is 7902, 7698, or 7839, both with and without using bulk binds:

DECLARE
TYPE Numlist IS VARRAY (100) OF NUMBER;
Id NUMLIST := NUMLIST(7902, 7698, 7839);
BEGIN

-- Efficient method, using a bulk bind


FORALL i IN Id.FIRST..Id.LAST -- bulk-bind the VARRAY
UPDATE emp SET Sal = 1.1 * Sal
WHERE Mgr = Id(i);

-- Slower method, running the UPDATE statements within a regular loop


FOR i IN Id.FIRST..Id.LAST LOOP
UPDATE emp SET Sal = 1.1 * Sal
WHERE Mgr = Id(i);
END LOOP;
END;
/

Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee that is updated, leading to context switches that hurt performance. If you have a set of rows
prepared in a PL/SQL table, you can bulk-insert or bulk-update the data using a loop which improves performance:

FORALL i in Emp_Data.FIRST..Emp_Data.LAST
INSERT INTO emp VALUES(Emp_Data(i));

SELECT Statements that Reference Collections

The BULK COLLECT INTO clause can improve the performance of queries that reference collections. For example, the following PL/SQL block queries multiple values into PL/SQL tables,
both with and without bulk binds:

-- Find all employees whose manager's ID number is 7698.


DECLARE
TYPE Var_tab IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
Empno VAR_TAB;
Ename VAR_TAB;
Counter NUMBER;

https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&doctype=BULLETIN... 7/8/2010
Page 2 of 5

CURSOR C IS
SELECT Empno, Ename FROM emp WHERE Mgr = 7698;
BEGIN

-- Efficient method, using a bulk bind


SELECT Empno, Ename BULK COLLECT INTO Empno, Ename
FROM emp
WHERE Mgr = 7698;

-- Slower method, assigning each collection element within a loop.

counter := 1;
FOR rec IN C LOOP
Empno(Counter) := rec.Empno;
Ename(Counter) := rec.Ename;
Counter := Counter + 1;
END LOOP;
END;
/

You can use BULK COLLECT INTO with tables of scalar values, or tables of %TYPE values. Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee
that is selected, leading to context switches that slow performance.

FOR Loops that Reference Collections and the Returning Into Clause

You can use the FORALL keyword along with the BULK COLLECT INTO keyword to improve the performance of FOR loops that reference collections and return DML. For example, the
following PL/SQL block updates the emp table by computing bonuses for a collection of employees; then it returns the bonuses in a column called bonlist. The actions are performed both
with and without using bulk binds:

DECLARE
TYPE Emplist IS VARRAY(100) OF NUMBER;
Empids EMPLIST := EMPLIST(7369, 7499, 7521, 7566, 7654, 7698);
TYPE Bonlist IS TABLE OF emp.sal%TYPE;
Bonlist_inst BONLIST := BONLIST(1,2,3,4,5);
BEGIN

FORALL i IN Empids.FIRST..Empids.LAST
UPDATE emp SET sal = 0.1 * Sal
WHERE Empno = Empids(i)
RETURNING Sal BULK COLLECT INTO Bonlist_inst;

FOR i IN Empids.FIRST..Empids.LAST LOOP


UPDATE emp Set sal = 0.1 * sal
WHERE Empno = Empids(i)
RETURNING Sal INTO Bonlist_inst(i);
END LOOP;
END;
/

Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee that is updated, leading to context switches that hurt performance.

Control Memory Used in a BULK COLLECT with LIMIT Keyword

The most important thing to remember when you learn about and start to take advantage of features such as BULK COLLECT is that there is no free lunch. There is almost always a trade-
off to be made somewhere. The tradeoff with BULK COLLECT, like so many other performance-enhancing features, is "run faster but consume more memory."

Specifically, memory for collections is stored in the program global area (PGA), not the system global area (SGA). SGA memory is shared by all sessions connected to the Oracle
Database, but PGA memory is allocated for each session. Thus, if a program requires 5MB of memory to populate a collection and there are 100 simultaneous connections, that program
causes the consumption of 500MB of PGA memory, in addition to the memory allocated to the SGA.

Fortunately, PL/SQL makes it easy for developers to control the amount of memory used in a BULK COLLECT operation by using the LIMIT clause. Suppose I need to retrieve all the rows
from the emp table and then perform some operation on each row. I can use BULK COLLECT as follows:

CREATE OR REPLACE PROCEDURE process_all_rows


IS
TYPE emps IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER;
l_emps emps;
BEGIN
SELECT * BULK COLLECT INTO l_emps
FROM emp;
FOR indx IN 1 .. l_emps.COUNT LOOP
dbms_output.put_line('Employee processed: ' || l_emps(indx).ename);
END LOOP;
END process_all_rows;
/

Very concise, elegant, and efficient code. If, however, my emp table contains tens of thousands of rows, each of which contains hundreds of columns, this program can cause excessive
PGA memory consumption. Consequently, you should avoid this sort of "unlimited" use of BULK COLLECT. Instead, move the SELECT statement into an explicit cursor declaration and
then use a simple loop to fetch many, but not all, rows from the table with each execution of the loop body, as shown below.

Using BULK COLLECT with LIMIT clause

CREATE OR REPLACE PROCEDURE process_all_rows(limit_in IN PLS_INTEGER DEFAULT 100) IS


CURSOR emp_cur IS
SELECT * FROM emp;
TYPE emps IS TABLE OF emp_cur%ROWTYPE INDEX BY PLS_INTEGER;
l_emps emps;
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur
BULK COLLECT INTO l_emps LIMIT limit_in;
FOR indx IN 1 .. l_emps.COUNT
LOOP
dbms_output.put_line('Employee processed: ' || l_emps(indx).ename);
END LOOP;
EXIT WHEN l_emps.COUNT < limit_in;
END LOOP;
CLOSE emp_cur;
END process_all_rows;
/

https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&doctype=BULLETIN... 7/8/2010
Page 3 of 5

The process_all_rows procedure given above requests that up to the value of limit_in rows be fetched at a time. PL/SQL will reuse the same limit_in elements in the collection each time
the data is fetched and thus also reuse the same memory. Even if my table grows in size, the PGA consumption will remain stable.

Restriction removed while using Bulk Binding

1. ORA-22160 with FORALL

Scenarios in which we must go through our collection of "candidate" data for inserts and remove some (perhaps all) of the rows before doing the insert. When we try to use FORALL, we
get this error message:
ORA-22160: element at index [2750] does not exist

How can we avoid this error and get all our data inserted?

FORALL is an important enhancement to PL/SQL since Oracle8i was released. In the Oracle8i and Oracle9i Database, the only format with which you could use FORALL was this:

FORALL index_variable
IN low_value .. high_value
<DML_Statement>;

As well as a "regular" numeric FOR loop, FORALL will iterate through each integer between low_value and high_value, using that integer to identify an element in all collections that are
bound into the DML statement with the index_variable. If no element exists at a particular index value, Oracle Database raises an exception, as you can see below.

Raising ORA-22160

CREATE TABLE first_names (name VARCHAR2(50))


/
DECLARE
TYPE list_of_names IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
family list_of_names;
BEGIN
family (1) := 'Eli';
family (2) := 'Chris';
family (3) := 'Veva';
family (5) := 'Steven';
FORALL indx IN family.FIRST .. family.LAST
INSERT INTO first_names VALUES (family (indx));
END;
/
DECLARE
*
ERROR at line 1:
ORA-22160: element at index [4] does not exist
ORA-06512: at line 9

FORALL, in other words, requires a sequentially or densely filled collection. Now if you were still running Oracle8i or Oracle9i Database and wanted to fix this problem, you would have to
copy the data from your sparsely filled collection over to one without any gaps. From a performance standpoint, this is nothing to worry about; manipulating collections is very fast. But it
does involve writing and maintaining even more code.

In Oracle Database 10g, Oracle added two new clauses to the FORALL statement: INDICES OF and VALUES OF. They allow you to avoid the restriction on using densely filled
collections. The INDICES OF clause populates a sparse collection by using only index values that are defined. The VALUES OF clause use only index values that are found in the
elements of another defined collection.

Here is a rewrite of the code given above that avoids the ORA-22160 error.

DECLARE
TYPE list_of_names IS TABLE OF VARCHAR2 (50) INDEX BY PLS_INTEGER;
family list_of_names;
BEGIN
family (1) := 'Eli';
family (2) := 'Chris';
family (3) := 'Veva';
family (5) := 'Steven';
FORALL indx IN INDICES OF family
INSERT INTO first_names VALUES (family(indx));
END;
/

The VALUES OF clause is useful when you want to use only a subset of the collection within the DML statement. For example, below is a procedure that accepts a collection of emp
records and should insert only records for emps with a salary of $10,000 or more.

CREATE TABLE emp_copy as select * from emp;

CREATE OR REPLACE PACKAGE emp_dml


IS
TYPE emps IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER;
PROCEDURE insert_some (emp_in IN emps);
END emp_dml;
/

CREATE OR REPLACE PACKAGE BODY emp_dml


IS
PROCEDURE insert_some (emp_in IN emps)
IS
TYPE index_values IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
l_values_of index_values;
l_index PLS_INTEGER;
BEGIN
-- Only insert those employees with a salary >= 10000.
l_index := emp_in.FIRST;

WHILE (l_index IS NOT NULL) LOOP


IF emp_in (l_index).sal >= 10000
THEN
l_values_of (l_values_of.COUNT + 1) := l_index;
END IF;
l_index := emp_in.NEXT (l_index);
END LOOP;

FORALL indx IN VALUES OF l_values_of


INSERT INTO emp_copy VALUES emp_in (indx);

https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&doctype=BULLETIN... 7/8/2010
Page 4 of 5

END insert_some;
END emp_dml;
/

The code populates a row in l_values_of with the index value from emp_in, only if the salary in that record is at least $10,000. Thus, with the FORALL statement, the VALUES OF clause
ensures that all other employee records are ignored.

The following script can be run to verify the behavior of the emp_dml package.

Verifying behavior of employees_dml package

SELECT COUNT(*)
FROM emp
WHERE sal < 10000
/

DECLARE
l_emp emp_dml.emps;
BEGIN
SELECT * BULK COLLECT INTO l_emp
FROM emp;
emp_dml.insert_some(l_emp);
END;
/

SELECT COUNT(*)
FROM emp_copy
WHERE sal < 10000
/

Finally, you can also use the INDICES OF clause with an entirely different collection that serves as a kind of filter for the collections used in the DML statement. Code provided below
shows an example of this approach.

SQL> select sal, empno, ename from emp_copy where empno in (7369, 7499, 7521);

SAL EMPNO ENAME


---------------
0 7369 SMITH
0 7499 ALLEN
0 7521 WARD

--Run the following code to set the salary of Smith and Ward to 10000
DECLARE
TYPE emps IS TABLE OF emp_copy.empno%TYPE INDEX BY PLS_INTEGER;
l_emps emps;
TYPE boolean_emp IS TABLE OF Boolean INDEX BY PLS_INTEGER;
l_emps_index boolean_emp;
BEGIN
l_emps (1) := 7369;
l_emps (100) := 7499;
l_emps (500) := 7521;

l_emps_index (1) := false;


l_emps_index (500) := true;
l_emps_index (799) := null;

FORALL indx IN INDICES OF l_emps_index


BETWEEN 1 AND 500
UPDATE emp_copy
SET sal = 10000
WHERE empno = l_emps(indx);
END;
/

SQL> select sal, empno, ename from emp_copy where empno in (7369, 7499, 7521);

SAL EMPNO ENAME


----------------
10000 7369 SMITH
0 7499 ALLEN
10000 7521 WARD

This code uses the index values of defined elements in the l_emp_index collection to specify which elements of the l_emps collection to use in the update statement. Note that a
BETWEEN clause is added to constrain which of the index values of l_emp_index will be used.

2. PLS-00436 Restriction in FORALL

The PLS-00436 restriction has been removed from 11g. Each element of a collection object can now be referenced using SET and WHERE clauses of a DML statement in a FORALL
construct.

Note 871666.1 : PLS-00436 Restriction in FORALL Statements Removed From 11g.

Related

Products

• Oracle Database Products > Oracle Database > Application Development > PL/SQL

Keywords

FORALL
Errors

ORA-6512; ORA-22160; PLS-436; 22160 ERROR

https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&doctype=BULLETIN... 7/8/2010
Page 5 of 5

Back to top

https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&doctype=BULLETIN... 7/8/2010

You might also like