Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Semestr2 / 1 - Oracle / PL_SQL / b14261.pdf
Скачиваний:
27
Добавлен:
12.05.2015
Размер:
4.36 Mб
Скачать

Reducing Loop Overhead for DML Statements and Queries with Bulk SQL

Example 11–9 Bulk Operation That Continues Despite Exceptions

-- 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 < '30-DEC-94';

--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

WHEN dml_errors THEN -- Now we figure out what failed and why. 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;

The output from the example is similar to:

Number of statements that failed: 2

Error #1 occurred during iteration #7

Error message is ORA-12899: value too large for column

Error #2 occurred during iteration #13

Error message is ORA-12899: value too large for column

In Example 11–9, PL/SQL raises predefined exceptions because updated values were too large to insert into the job_id column. After the FORALL statement, SQL%BULK_EXCEPTIONS.COUNT returned 2, and the contents of SQL%BULK_EXCEPTIONS were (7,12899) and (13,12899).

To get the Oracle error message (which includes the code), the value of

SQL%BULK_EXCEPTIONS(i).ERROR_CODE was negated and then passed to the error-reporting function SQLERRM, which expects a negative number.

Retrieving Query Results into Collections with the BULK COLLECT Clause

Using the keywords BULK COLLECT with a query is a very efficient way to retrieve the result set. Instead of looping through each row, you store the results in one or more collections, in a single operation. You can use these keywords in the SELECT INTO and FETCH INTO statements, and the RETURNING INTO clause.

Tuning PL/SQL Applications for Performance 11-15

Reducing Loop Overhead for DML Statements and Queries with Bulk SQL

With the BULK COLLECT clause, all the variables in the INTO list must be collections. The table columns can hold scalar or composite values, including object types. Example 11–10 loads two entire database columns into nested tables:

Example 11–10 Retrieving Query Results With BULK COLLECT

DECLARE

TYPE NumTab IS TABLE OF employees.employee_id%TYPE;

TYPE NameTab IS TABLE OF employees.last_name%TYPE;

enums NumTab;

-- No need to initialize the collections.

names NameTab;

-- Values will be filled in by the SELECT INTO.

PROCEDURE print_results IS

BEGIN

IF enums.COUNT = 0 THEN DBMS_OUTPUT.PUT_LINE('No results!');

ELSE DBMS_OUTPUT.PUT_LINE('Results:'); FOR i IN enums.FIRST .. enums.LAST LOOP

DBMS_OUTPUT.PUT_LINE(' Employee #' || enums(i) || ': ' || names(i)); END LOOP;

END IF; END;

BEGIN

--Retrieve data for employees with Ids greater than 1000 SELECT employee_id, last_name

BULK COLLECT INTO enums, names FROM employees WHERE employee_id > 1000;

--The data has all been brought into memory by BULK COLLECT

--No need to FETCH each row from the result set print_results();

--Retrieve approximately 20% of all rows

SELECT employee_id, last_name

BULK COLLECT INTO enums, names FROM employees SAMPLE (20); print_results();

END;

/

The collections are initialized automatically. Nested tables and associative arrays are extended to hold as many elements as needed. If you use varrays, all the return values must fit in the varray's declared size. Elements are inserted starting at index 1, overwriting any existing elements.

Because the processing of the BULK COLLECT INTO clause is similar to a FETCH loop, it does not raise a NO_DATA_FOUND exception if no rows match the query. You must check whether the resulting nested table or varray is null, or if the resulting associative array has no elements, as shown in Example 11–10.

To prevent the resulting collections from expanding without limit, you can use the LIMIT clause to or pseudocolumn ROWNUM to limit the number of rows processed. You can also use the SAMPLE clause to retrieve a random sample of rows.

Example 11–11 Using the Pseudocolumn ROWNUM to Limit Query Results

DECLARE

TYPE SalList IS TABLE OF employees.salary%TYPE; sals SalList;

BEGIN

--Limit the number of rows to 50

SELECT salary BULK COLLECT INTO sals FROM employees WHERE ROWNUM <= 50;

11-16 Oracle Database PL/SQL User’s Guide and Reference

Reducing Loop Overhead for DML Statements and Queries with Bulk SQL

--Retrieve 10% (approximately) of the rows in the table

SELECT salary BULK COLLECT INTO sals FROM employees SAMPLE (10);

END;

/

You can process very large result sets by fetching a specified number of rows at a time from a cursor, as shown in the following sections.

Examples of Bulk-Fetching from a Cursor

You can fetch from a cursor into one or more collections as shown in Example 11–12.

Example 11–12 Bulk-Fetching from a Cursor Into One or More Collections

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; names NameList;

sals SalList;

TYPE RecList IS TABLE OF c1%ROWTYPE; recs RecList;

v_limit PLS_INTEGER := 10; PROCEDURE print_results IS BEGIN

IF names IS NULL OR names.COUNT = 0 THEN -- check if collections are empty DBMS_OUTPUT.PUT_LINE('No results!');

ELSE

DBMS_OUTPUT.PUT_LINE('Results: '); 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 at once ---'); 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 the columns from the result set come from a single record DBMS_OUTPUT.PUT_LINE(' Employee ' || recs(i).last_name || ': $'

|| recs(i).salary); END LOOP;

END;

/

Tuning PL/SQL Applications for Performance 11-17

Reducing Loop Overhead for DML Statements and Queries with Bulk SQL

Example 11–13 shows how you can fetch from a cursor into a collection of records.

Example 11–13 Bulk-Fetching from a Cursor Into a Collection of Records

DECLARE

TYPE DeptRecTab IS TABLE OF departments%ROWTYPE; dept_recs DeptRecTab;

CURSOR c1 IS

SELECT department_id, department_name, manager_id, location_id FROM departments WHERE department_id > 70;

BEGIN OPEN c1;

FETCH c1 BULK COLLECT INTO dept_recs; END;

/

Limiting the Rows for a Bulk FETCH Operation with the LIMIT Clause

The optional LIMIT clause, allowed only in bulk FETCH statements, limits the number of rows fetched from the database. In Example 11–14, with each iteration of the loop, the FETCH statement fetches ten rows (or less) into index-by table empids. The previous values are overwritten. Note the use of empids.COUNT to determine when to exit the loop.

Example 11–14 Using LIMIT to Control the Number of Rows In a BULK COLLECT

DECLARE

TYPE numtab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;

CURSOR c1 IS SELECT employee_id FROM employees WHERE department_id = 80;

empids

numtab;

rows

PLS_INTEGER := 10;

BEGIN

 

OPEN c1;

LOOP -- the following statement fetches 10 rows or less in each iteration FETCH c1 BULK COLLECT INTO empids LIMIT rows;

EXIT WHEN empids.COUNT = 0;

--EXIT WHEN c1%NOTFOUND; -- incorrect, can omit some data DBMS_OUTPUT.PUT_LINE('------- Results from Each Bulk Fetch --------');

FOR i IN 1..empids.COUNT LOOP

DBMS_OUTPUT.PUT_LINE( 'Employee Id: ' || empids(i)); END LOOP;

END LOOP; CLOSE c1;

END;

/

Retrieving DML Results into a Collection with the RETURNING INTO Clause

You can use the BULK COLLECT clause in the RETURNING INTO clause of an INSERT, UPDATE, or DELETE statement:

Example 11–15 Using BULK COLLECT With the RETURNING INTO Clause

CREATE TABLE emp_temp AS SELECT * FROM employees; DECLARE

TYPE NumList IS TABLE OF employees.employee_id%TYPE; enums NumList;

TYPE NameList IS TABLE OF employees.last_name%TYPE; names NameList;

BEGIN

11-18 Oracle Database PL/SQL User’s Guide and Reference

Соседние файлы в папке PL_SQL