Examples Using FORALL and BULK COLLECT

TimesTen supports bulk binding and the FORALL statement and BULK COLLECT feature.

See FORALL and BULK COLLECT Operations.

Examples in this section cover the following:

Using FORALL with SQL%BULK_ROWCOUNT

The %BULK_ROWCOUNT cursor attribute is a composite structure designed for use with the FORALL statement.

The attribute acts like an associative array (index-by table). Its ith element stores the number of rows processed by the ith execution of the INSERT statement. If the ith execution affects no rows, then %BULK_ROWCOUNT(i) returns zero.

This is demonstrated in the following example.

Command> DECLARE
           TYPE num_list_type IS TABLE OF NUMBER
              INDEX BY BINARY_INTEGER;
         v_nums num_list_type;
         BEGIN
           v_nums (1) := 1;
           v_nums (2) := 3;
           v_nums (3) := 5;
           v_nums (4) := 7;
           v_nums (5) := 11;
             FORALL i IN v_nums.FIRST .. v_nums.LAST
              INSERT INTO num_table (n) VALUES (v_nums (i));
            FOR i IN v_nums.FIRST .. v_nums.LAST
            LOOP
               DBMS_OUTPUT.PUT_LINE ('Inserted '||
                 SQL%BULK_ROWCOUNT (i) || ' row (s)' ||
                 ' on iteration  ' || i );
            END LOOP;
         END;
         /
Inserted 1 row (s) on iteration  1
Inserted 1 row (s) on iteration  2
Inserted 1 row (s) on iteration  3
Inserted 1 row (s) on iteration  4
Inserted 1 row (s) on iteration  5
 
PL/SQL procedure successfully completed.

Using BULK COLLECT INTO with Queries

Use BULK COLLECT with the SELECT statement in PL/SQL to retrieve rows without using a cursor.

This example selects all rows from the departments table for a specified location into a nested table, then uses a FOR LOOP to output data.

Command> CREATE OR REPLACE PROCEDURE get_departments (p_loc NUMBER) IS
            TYPE dept_tab_type IS
         TABLE OF departments%ROWTYPE;
           v_depts dept_tab_type;
          BEGIN
            SELECT * BULK COLLECT INTO v_depts
           FROM departments
          where location_id = p_loc;
          FOR i IN 1 .. v_depts.COUNT
          LOOP
             DBMS_OUTPUT.PUT_LINE (v_depts(i).department_id
               || ' ' || v_depts (i).department_name);
          END LOOP;
          END;
          /
 
Procedure created.

The following executes the procedure and verifies the results:

Command> EXECUTE GET_DEPARTMENTS (1700);
10 Administration
30 Purchasing
90 Executive
100 Finance
110 Accounting
120 Treasury
130 Corporate Tax
140 Control And Credit
150 Shareholder Services
160 Benefits
170 Manufacturing
180 Construction
190 Contracting
200 Operations
210 IT Support
220 NOC
230 IT Helpdesk
240 Government Sales
250 Retail Sales
260 Recruiting
270 Payroll
 
PL/SQL procedure successfully completed.
 
Command> SELECT department_id, department_name FROM departments WHERE
         location_id = 1700;
< 10, Administration >
< 30, Purchasing >
< 90, Executive >
< 100, Finance >
< 110, Accounting >
< 120, Treasury >
< 130, Corporate Tax >
< 140, Control And Credit >
< 150, Shareholder Services >
< 160, Benefits >
< 170, Manufacturing >
< 180, Construction >
< 190, Contracting >
< 200, Operations >
< 210, IT Support >
< 220, NOC >
< 230, IT Helpdesk >
< 240, Government Sales >
< 250, Retail Sales >
< 260, Recruiting >
< 270, Payroll >
21 rows found.

Using BULK COLLECT INTO with Cursors

This example uses a cursor to bulk-collect rows from the departments table with a specified location_id. value.

Results are the same as in the preceding section, Using BULK COLLECT INTO with Queries.

Command> CREATE OR REPLACE PROCEDURE get_departments2 (p_loc NUMBER) IS
           CURSOR cur_dept IS
            SELECT * FROM departments
            WHERE location_id = p_loc;
          TYPE dept_tab_type IS TABLE OF cur_dept%ROWTYPE;
          v_depts dept_tab_type;
         BEGIN
           OPEN cur_dept;
           FETCH cur_dept BULK COLLECT INTO v_depts;
           CLOSE cur_dept;
         FOR i IN 1 .. v_depts.COUNT
          LOOP
            DBMS_OUTPUT.PUT_LINE (v_depts (i).department_id
            || ' ' || v_depts (i).department_name );
         END LOOP;
         END;
         /
 
Procedure created.
 
Command> EXECUTE GET_DEPARTMENTS2 (1700);
10 Administration
30 Purchasing
90 Executive
100 Finance
110 Accounting
120 Treasury
130 Corporate Tax
140 Control And Credit
150 Shareholder Services
160 Benefits
170 Manufacturing
180 Construction
190 Contracting
200 Operations
210 IT Support
220 NOC
230 IT Helpdesk
240 Government Sales
250 Retail Sales
260 Recruiting
270 Payroll
 
PL/SQL procedure successfully completed.

Using SAVE EXCEPTIONS with BULK COLLECT

SAVE EXCEPTIONS enables an UPDATE, INSERT, or DELETE statement to continue executing after it issues an exception. When the statement finishes, an error is issued to signal that at least one exception occurred. Exceptions are collected into an array that you can examine using %BULK_EXCEPTIONS after the statement has executed.

In this example, PL/SQL raises predefined exceptions because some new values are too large for the job_id column. After the FORALL statement, SQL%BULK_EXCEPTIONS.COUNT returns 2, and the contents of SQL%BULK_EXCEPTIONS are (7, 01401) and (13, 01401), indicating the error number and the line numbers where the error was detected. To get the error message, the negative of SQL%BULK_EXCEPTIONS(i).ERROR_CODE is passed to the error-reporting function SQLERRM (which expects a negative number).

The following script is executed using ttIsql:

-- create a temporary table for this example
CREATE TABLE emp_temp AS SELECT * FROM employees;
 
DECLARE
   TYPE empid_tab IS TABLE OF employees.employee_id%TYPE;
   emp_sr empid_tab;
-- create an exception handler for ORA-24381
   errors NUMBER;
   dml_errors EXCEPTION;
   PRAGMA EXCEPTION_INIT(dml_errors, -24381);

BEGIN
   SELECT employee_id
      BULK COLLECT INTO emp_sr FROM emp_temp
      WHERE hire_date < '1994-12-30';
-- add '_SR' to the job_id of the most senior employees
   FORALL i IN emp_sr.FIRST..emp_sr.LAST SAVE EXCEPTIONS
      UPDATE emp_temp SET job_id = job_id || '_SR'
      WHERE emp_sr(i) = emp_temp.employee_id;
-- If any errors occurred during the FORALL SAVE EXCEPTIONS,
-- a single exception is raised when the statement completes.
 
EXCEPTION
-- Figure out what failed and why
   WHEN dml_errors THEN
      errors := SQL%BULK_EXCEPTIONS.COUNT;
      DBMS_OUTPUT.PUT_LINE
         ('Number of statements that failed: ' || errors);
      FOR i IN 1..errors LOOP
         DBMS_OUTPUT.PUT_LINE('Error #' || i || ' occurred during '||
                 'iteration #' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
         DBMS_OUTPUT.PUT_LINE('Error message is ' ||
         SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
   END LOOP;
END;
/
 
DROP TABLE emp_temp;

Results are as follows:

Number of statements that failed: 2
Error #1 occurred during iteration #7
Error message is ORA-01401: inserted value too large for column
Error #2 occurred during iteration #13
Error message is ORA-01401: inserted value too large for column
 
PL/SQL procedure successfully completed.