Note: Without the bulk bind, PL/SQL
sends a SQL statement to the SQL engine for each record that is inserted, updated,
or deleted leading to context switches that hurt performance.
BULK COLLECT
BULK COLLECT Syntax
FETCH BULK COLLECT <cursor_name>
BULK COLLECT INTO <collection_name> LIMIT <numeric_expression>;
or
FETCH BULK COLLECT <cursor_name> BULK COLLECT INTO <array_name> LIMIT <numeric_expression>;
DECLARE
j PLS_INTEGER := 1;
k parent.part_name%TYPE := 'Transducer'; BEGIN FOR i IN 1 .. 200000 LOOP SELECT DECODE(k, 'Transducer', 'Rectifier',
'Rectifier', 'Capacitor',
'Capacitor', 'Knob',
'Knob', 'Chassis',
'Chassis', 'Transducer') INTO k FROMDUAL;
DECLARE
-- collections to hold a set of customer names and amounts SUBTYPE cust_name IS valid_orders.cust_name%TYPE;
TYPE cust_typ ISTABLE OF cust_name;
cust_tab cust_typ;
SUBTYPE order_amount IS valid_orders.amount%TYPE;
TYPE amount_typ ISTABLE OF NUMBER;
amount_tab amount_typ;
-- collections to point into the CUST_TAB collection.
TYPE index_pointer_t ISTABLE OF PLS_INTEGER;
PROCEDURE setup_data IS BEGIN
-- Set up sample order data, with some invalid and 'big' orders
cust_tab := cust_typ('Company1', 'Company2', 'Company3',
'Company4', 'Company5');
amount_tab := amount_typ(5000.01, 0, 150.25, 4000.00, NULL); END setup_data;
dbms_output.put_line('--- Original order data ---'); FOR i IN 1..cust_tab.LAST LOOP
dbms_output.put_line('Cust#' || i || ', '|| cust_tab(i) ||
': $'||amount_tab(i)); ENDLOOP;
-- Delete invalid orders (where amount is null or 0) FOR i IN 1..cust_tab.LAST LOOP IF amount_tab(i) is null or amount_tab(i) = 0 THEN
cust_tab.delete(i);
amount_tab.delete(i); ENDIF; ENDLOOP;
dbms_output.put_line('---Data with deleted invalid orders---');
FOR i IN 1..cust_tab.LASTLOOP IF cust_tab.EXISTS(i) THEN
dbms_output.put_line('Cust#' || i || ', ' || cust_tab(i) ||
': $'||amount_tab(i)); ENDIF; ENDLOOP;
-- Since the subscripts of our collections are not consecutive,
-- we use use FORRALL...INDICES OF to iterate the subscripts FORALL i IN INDICES OF cust_tab INSERTINTO valid_orders
(cust_name, amount) VALUES
(cust_tab(i), amount_tab(i));
-- Now let's process the order data differently extracting
-- 2 subsets and storing each subset in a different table.
setup_data; -- Reinitialize the CUST_TAB and AMOUNT_TAB collections
FOR i IN cust_tab.FIRST .. cust_tab.LAST LOOP IF amount_tab(i) ISNULL OR amount_tab(i) = 0 THEN
-- add a new element to the collection
rejected_order_tab.EXTEND;
-- record original collection subscript
rejected_order_tab(rejected_order_tab.LAST) := i; ENDIF;
IF amount_tab(i) > 2000 THEN
-- Add a new element to the collection
big_order_tab.EXTEND;
-- record original collection subscript
big_order_tab(big_order_tab.LAST) := i; ENDIF; ENDLOOP;
-- run one DML statement on one subset of elements,
-- and another DML statement on a different subset.
FORALL i INVALUES OF rejected_order_tab INSERTINTO rejected_orders VALUES (cust_tab(i), amount_tab(i));
-- SAVE EXCEPTIONS means don't stop if some DELETES fail FORALL i IN 1..l_data.COUNT SAVE EXCEPTIONS INSERTINTO tmp_target VALUES l_data(i);
-- If any errors occurred during the FORALL SAVE EXCEPTIONS,
-- a single exception is raised when the statement completes. EXITWHEN c%NOTFOUND; ENDLOOP; EXCEPTION WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
dbms_output.put_line('Number of DELETE statements that
failed: ' || errors);
FOR i IN 1 .. errors LOOP
dbms_output.put_line('Error #' || i || ' at '|| 'iteration
#' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
dbms_output.put_line('Error message is ' ||
SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); ENDLOOP; WHENOTHERSTHEN RAISE;
END forall_errors;
/
cai test.cust_account_id%TYPE;
cid test.carrier_id%TYPE;
ecode NUMBER;
iud stage.upd_date%TYPE;
sd test.sub_date%TYPE;
tid test.ticket_id%TYPE;
upd test.upd_date%TYPE; BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO l_data LIMIT 50000;
-- insert all exceptions into the
load_errors table FOR j IN 1 ..
errors LOOP
ecode := SQL%BULK_EXCEPTIONS(j).ERROR_CODE;
sd :=
TRUNC(l_data(SQL%BULK_EXCEPTIONS(j).ERROR_INDEX).sub_date);
cai :=
l_data(SQL%BULK_EXCEPTIONS(j).ERROR_INDEX).cust_account_id;
cid :=
l_data(SQL%BULK_EXCEPTIONS(j).ERROR_INDEX).carrier_id;
tid :=
l_data(SQL%BULK_EXCEPTIONS(j).ERROR_INDEX).ticket_id;
-- for each record in load_errors
process those that can
-- be handled and delete them after successful handling FOR le_rec IN le_cur LOOP IF le_rec.error_code = 1 THEN SELECT upd_date INTO iud FROM test WHERE cust_account_id =
le_rec.cust_account_id AND carrier_id = le_rec.carrier_id AND ticket_id = le_rec.ticket_id;
IF iud ISNULLTHEN RAISE; ELSIF iud < le_rec.upd_date THEN UPDATE test
SET upd_date =
le_rec.upd_date WHERE sub_date =
le_rec.sub_date AND cust_account_id =
le_rec.cust_account_id AND carrier_id =
le_rec.carrier_id AND ticket_id =
le_rec.ticket_id; ELSE RAISE; ENDIF; ENDIF; ENDLOOP; COMMIT; -- Exits here when any existing found. END array_exceptions;
/
CURSOR c IS SELECT * FROM all_objects WHERESUBSTR(object_name,1,1) BETWEEN 'A' AND 'W';
BEGIN OPEN c; LOOP FETCH c BULK COLLECT INTO
l_owner, l_object_name, l_subobject_name, l_object_id,
l_data_object_id, l_object_type, l_created,
l_last_ddl_time, l_timestamp, l_status, l_temporary,
l_generated, l_secondary LIMIT p_array_size;
FORALL i in 1 .. l_owner.COUNT INSERTINTO t
(owner, object_name, subobject_name, object_id,
data_object_id, object_type, created, last_ddl_time,
timestamp, status, temporary, generated, secondary) VALUES
(l_owner(i), l_object_name(i), l_subobject_name(i),
l_object_id(i), l_data_object_id(i),
l_object_type(i), l_created(i), l_last_ddl_time(i),
l_timestamp(i), l_status(i), l_temporary(i),
l_generated(i), l_secondary(i)); EXITWHEN c%NOTFOUND; ENDLOOP; COMMIT; CLOSE c; END nrows_at_a_time;
/