PL/SQL中的批量操作,允许一次操作和处理多行数据,而不是一次处理一行数据。批量操作包括批量的读取数据,批量的绑定处理处理数据:( bulk fetching and bulk binding)
Bulk fetching refers to the ability to retrieve a set of rows from the database into PL/SQL structures with a single call, rather than making a call to the database for each row to be retrieved.
Bulk binding lets you perform the converse: to take those sets of rows stored in
PL/SQL structures and save them to the database in an efficient manner.
-------------------------------------------------------------------------------------------------
Getting Started with BULK Fetch
在PL/SQL中,有三种方法获取行记录:
1、Implicit Cursor
SELECT INTO
(当没有查询到记录,或者查询到的记录多于一行会抛出异常--NO_DATA_FOUND和TO_MANY_ROWS)
1 DECLARE 2 l_Descr Hardware.Descr%TYPE; 3 BEGIN 4 SELECT Descr 5 INTO l_Descr 6 FROM Hardware 7 WHERE Aisle = 1 8 AND Item = 1; 9 END;
2. Explicit Fetch Calls(手动i打开游标,进行游标的关闭和读取数据)
1 DECLARE 2 CURSOR c_Tool_List IS 3 SELECT Descr 4 FROM Hardware 5 WHERE Aisle = 1 6 AND Item BETWEEN 1 AND 500; 7 l_Descr Hardware.Descr%TYPE; 8 BEGIN 9 OPEN c_Tool_List; 10 LOOP 11 FETCH c_Tool_List 12 INTO l_Descr; 13 EXIT WHEN c_Tool_List%NOTFOUND; 14 END LOOP; 15 CLOSE c_Tool_List; 16 END;
3、Implicit Fetch Calls(FOR LOOP循环,游标FOR循环--推荐使用,因为不要进行游标的关闭等)
1 BEGIN 2 FOR i IN (SELECT Descr 3 FROM Hardware 4 WHERE Aisle = 1 5 AND Item BETWEEN 1 AND 500) LOOP 6 <processing code for each row> 7 END LOOP; 8 END;
上述三种情况对应的批量读取数据:
1. Implicit Cursor BULK Mode:SELECT BULK COLLECT INTO
1 DECLARE 2 TYPE t_Descr_List IS TABLE OF Hardware.Descr%TYPE; 3 l_Descr_List t_Descr_List; 4 BEGIN 5 SELECT Descr BULK COLLECT 6 INTO l_Descr_List 7 FROM Hardware 8 WHERE Aisle = 1 9 AND Item BETWEEN 1 AND 100; 10 END;
2. Explicit Fetch Calls BULK Mode:FETCH BULK COLLECT INTO
1 DECLARE 2 CURSOR c_Tool_List IS 3 SELECT Descr 4 FROM Hardware 5 WHERE Aisle = 1 6 AND Item BETWEEN 1 AND 500; 7 TYPE t_Descr_List IS TABLE OF c_Tool_List%ROWTYPE; 8 l_Descr_List t_Descr_List; 9 BEGIN 10 OPEN c_Tool_List; 11 FETCH c_Tool_List BULK COLLECT 12 INTO l_Descr_List; 13 CLOSE c_Tool_List; 14 END;
在这种情况下,假如使用LIMIT参数,限制每次处理的记录条数:
1 DECLARE 2 TYPE Numtab IS TABLE OF NUMBER INDEX BY PLS_INTEGER; 3 CURSOR C1 IS 4 SELECT Employee_Id FROM Employees WHERE Department_Id = 80; 5 Empids Numtab; 6 Rows PLS_INTEGER := 10; 7 BEGIN 8 OPEN C1; 9 -- Fetch 10 rows or less in each iteration 10 LOOP 11 FETCH C1 BULK COLLECT 12 INTO Empids LIMIT Rows; 13 EXIT WHEN Empids.Count = 0; 14 -- EXIT WHEN c1%NOTFOUND; -- incorrect, can omit some data 15 Dbms_Output.Put_Line('------- Results from Each Bulk Fetch --------'); 16 FOR i IN 1 .. Empids.Count LOOP 17 Dbms_Output.Put_Line('Employee Id: ' || Empids(i)); 18 END LOOP; 19 END LOOP; 20 CLOSE C1; 21 END;
使用
EXIT WHEN c1%NOTFOUND、使用该语句退出集合存在问题,因为他可能会忽略一些记录,
应该使用
EXIT WHEN Empids.Count = 0;来进行游标FOR循环的退出。
但是也可以在自己的业务处理完毕之后,使用
EXIT WHEN c1%NOTFOUND;来退出集合
3、Implicit Fetch Calls BULK mode
1 BEGIN 2 FOR i IN (SELECT Descr 3 FROM Hardware 4 WHERE Aisle = 1 5 AND Item BETWEEN 1 AND 500) LOOP 6 NULL; 7 END LOOP; 8 END;
三种形式的集合:
• Varray
• Nested table
• Associative array
-------------------------------------------------------------------------------------------------
Getting Started with BULK Fetch
:FORALL的例子
1 DECLARE 2 TYPE t_Row_List IS TABLE OF Hardware%ROWTYPE; 3 l_Row t_Row_List := t_Row_List(); 4 BEGIN 5 FOR i IN 1 .. 100 LOOP 6 l_Row.Extend; 7 l_Row(i).Aisle := 1; 8 l_Row(i).Item := i; 9 END LOOP; 10 FORALL i IN 1 .. 100 11 INSERT INTO Hardware VALUES l_Row (i); 12 END;
批量处理的标准步骤:
场景1:Elements Do Not Start at 1,序号没有从1开始,但是他还是连续的,可以使用.FIRST和.LAST作为循环的开始和结束:
1 DECLARE 2 TYPE t_Num_List IS TABLE OF Hardware.Item%TYPE INDEX BY PLS_INTEGER; 3 Val t_Num_List; 4 BEGIN 5 Val(10) := 10; 6 Val(11) := 20; 7 Val(12) := 20; 8 FORALL i IN Val.First .. Val.Last 9 INSERT INTO Hardware (Item) VALUES (Val(i)); 10 END;
场景2:Elements Are Not Contiguous,不联系的情况,可以使用Oracle提供的INDICES OF函数,
1 DECLARE 2 TYPE t_Num_List IS TABLE OF Hardware.Item%TYPE INDEX BY PLS_INTEGER; 3 Val t_Num_List; 4 BEGIN 5 Val(10) := 10; 6 -- val(11) := 20; 7 Val(12) := 20; 8 FORALL i IN INDICES OF Val 9 INSERT INTO Hardware (Item) VALUES (Val(i)); 10 END;
I recommend adopting a standard of using the INDICES OF clause whenever you want to process an entire collection, and that use of .FIRST, .LAST, and .COUNT should be deprecated in your PL/SQL code. Sadly, the INDICES OF extension can only be used in a FORALL statement, not in a standard FOR loop.(推荐在你的代码中使用INDICES OF来处理整个集合,FIRST,LAST,COUNT在你的PL/SQL编码不应该在出现,不幸的是,INDICES OF只可以用在FORALL自居中,不能使用在标准的LOOP循环中。)
VALUES OF使用:
1 DECLARE 2 TYPE t_Input_Row IS RECORD( 3 Item Hardware.Item%TYPE, 4 Descr Hardware.Descr%TYPE, 5 Status VARCHAR2(3)); 6 TYPE t_Input_List IS TABLE OF t_Input_Row INDEX BY PLS_INTEGER; 7 Src t_Input_List; 8 TYPE t_Target_Indices IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER; 9 Ind_New t_Target_Indices; 10 Ind_Upd t_Target_Indices; 11 BEGIN 12 FOR i IN 1 .. 100 LOOP 13 Src(i).Item := i; 14 Src(i).Descr := 'Item ' || i; 15 Src(i).Status := CASE 16 WHEN MOD(i, 5) = 0 THEN 17 'NEW' 18 ELSE 19 'UPD' 20 END; 21 END LOOP; 22 FOR i IN 1 .. 100 LOOP 23 IF Src(i).Status = 'NEW' THEN 24 Ind_New(Ind_New.Count) := i; 25 ELSE 26 Ind_Upd(Ind_Upd.Count) := i; 27 END IF; 28 END LOOP; 29 FORALL i IN VALUES OF Ind_New 30 INSERT INTO Hardware (Aisle, Item) VALUES (1, Src(i).Item); 31 Dbms_Output.Put_Line(SQL%ROWCOUNT || ' rows inserted'); 32 FORALL i IN VALUES OF Ind_Upd 33 UPDATE Hardware 34 SET Descr = Src(i).Descr 35 WHERE Aisle = 1 36 AND Item = Src(i).Item; 37 Dbms_Output.Put_Line(SQL%ROWCOUNT || ' rows updated'); 38 END;
--具体请参考:
<<Expert_PL_SQL_Practices_for_Oracle_Developers_and_DBAs>>:
Chapter 6: Bulk SQL Operations