1. 程式人生 > >Oracle資料庫的批量操作,forall,BULK COLLECT

Oracle資料庫的批量操作,forall,BULK COLLECT

oracle forall
FORALL語句的一個關鍵性改進,它可大大簡化程式碼,並且對於那些要在PL/SQL程式中更新很多行資料的程式來說,它可顯著提高其效能。
1:
用FORALL來增強DML的處理能力
Oracle為Oracle8i中的PL/SQL引入了兩個新的資料操縱語言(DML)語句:BULK COLLECT和FORALL。這兩個語句在PL/SQL內部進行一種陣列處理
;BULK COLLECT提供對資料的高速檢索,FORALL可大大改進INSERT、UPDATE和DELETE操作的效能。Oracle資料庫使用這些語句大大減少了
PL/SQL與SQL語句執行引擎的環境切換次數,從而使其效能有了顯著提高。

使用BULK COLLECT,你可以將多個行引入一個或多個集合中,而不是單獨變數或記錄中。下面這個BULK COLLECT的例項是將標題中包含
有”PL/SQL”的所有書籍檢索出來並置於記錄的一個關聯陣列中,它們都位於通向該資料庫的單一通道中。

DECLARE
TYPE books_aat
IS TABLE OF book%ROWTYPE
INDEX BY PLS_INTEGER;
books books_aat;
BEGIN
SELECT *
BULK COLLECT INTO book
FROM books
WHERE title LIKE '%PL/SQL%';
...
END;

類似地,FORALL將資料從一個PL/SQL集合傳送給指定的使用集合的表。下面的程式碼例項給出一個過程,即接收書籍資訊的一個巢狀表,並將該
集合(繫結陣列)的全部內容插入該書籍表中。注意,這個例子還利用了Oracle9i的FORALL的增強功能,可以將一條記錄直接插入到表中。
BULK COLLECT和FORALL都非常有用,它們不僅提高了效能,而且還簡化了為PL/SQL中的SQL操作所編寫的程式碼。下面的多行FORALL INSERT相當
清楚地說明了為什麼PL/SQL被認為是Oracle資料庫的最佳程式語言。

CREATE TYPE books_nt
IS TABLE OF book%ROWTYPE;
/ CREATE OR REPLACE PROCEDURE add_books ( books_in IN books_nt) IS BEGIN FORALL book_index IN books_in.FIRST .. books_in.LAST INSERT INTO book VALUES books_in(book_index); ... END;

不過在Oracle資料庫10g之前,以FORAll方式使用集合有一個重要的限制:該資料庫從IN範圍子句中的第一行到最後一行,依次讀取集合的內容
。如果在該範圍內遇到一個未定義的行,Oracle資料庫將引發ORA-22160異常事件:

ORA-22160: element at index [N] does not exist

對於FORALL的簡單應用,這一規則不會引起任何麻煩。但是,如果想盡可能地充分利用FORALL,那麼要求任意FORALL驅動陣列都要依次填充可
能會增加程式的複雜性並降低效能。
在Oracle資料庫10g中,PL/SQL現在在FORALL語句中提供了兩個新子句:INDICES OF與VALUES OF,它們使你能夠仔細選擇驅動陣列中該由擴充套件
DML語句來處理的行。
當繫結陣列為稀疏陣列或者包含有間隙時,INDICES OF會非常有用。該語句的語法結構為:
FORALL indx IN INDICES
OF sparse_collection
INSERT INTO my_table
VALUES sparse_collection (indx);
VALUES OF用於一種不同的情況:繫結陣列可以是稀疏陣列,也可以不是,但我只想使用該陣列中元素的一個子集。那麼我就可以使用VALUES
OF來指向我希望在DML操作中使用的值。該語句的語法結構為:
FORALL indx IN VALUES OF pointer_array
INSERT INTO my_table
VALUES binding_array (indx);
不用FOR迴圈而改用FORALL
假定我需要編寫一個程式,對合格員工(由comp_analysis.is_eligible函式確定)加薪,編寫關於不符合加薪條件的員工的報告並寫入
employee_history表。我在一個非常大的公司工作;我們的員工非常非常多。
對於一位PL/SQL開發人員來說,這並不是一項十分困難的工作。我甚至不需要使用BULK COLLECT或FORALL就可以完成這項工作,如清單 1所示
,我使用一個CURSOR FOR迴圈和單獨的INSERT及UPDATE語句。這樣的程式碼簡潔明瞭;不幸地是,我花了10分鐘來執行此程式碼,我的”老式”方法
要執行30分鐘或更長時間。
清單 1:

CREATE OR REPLACE PROCEDURE give_raises_in_department (
dept_in IN employee.department_id%TYPE
, newsal IN employee.salary%TYPE
)
IS
CURSOR emp_cur
IS
SELECT employee_id, salary, hire_date
FROM employee
WHERE department_id = dept_in;
BEGIN
FOR emp_rec IN emp_cur
LOOP
IF comp_analysis.is_eligible (emp_rec.employee_id)
THEN
UPDATE employee
SET salary = newsal
WHERE employee_id = emp_rec.employee_id;
ELSE
INSERT INTO employee_history
(employee_id, salary
, hire_date, activity
)
VALUES (emp_rec.employee_id, emp_rec.salary
, emp_rec.hire_date, 'RAISE DENIED'
);
END IF;
END LOOP;
END give_raises_in_department;

好在我公司的資料庫升級到了Oracle9i,而且更幸運的是,在最近的Oracle研討會上(以及Oracle技術網站提供的非常不錯的演示中)我瞭解
到了批量處理方法。所以我決定使用集合與批量處理方法重新編寫程式。寫好的程式如清單 2所示。
清單 2:

1 CREATE OR REPLACE PROCEDURE give_raises_in_department (
2 dept_in IN employee.department_id%TYPE
3 , newsal IN employee.salary%TYPE
4 )
5 IS
6 TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
7 INDEX BY PLS_INTEGER;
8 TYPE salary_aat IS TABLE OF employee.salary%TYPE
9 INDEX BY PLS_INTEGER;
10 TYPE hire_date_aat IS TABLE OF employee.hire_date%TYPE
11 INDEX BY PLS_INTEGER;
12
13 employee_ids employee_aat;
14 salaries salary_aat;
15 hire_dates hire_date_aat;
16
17 approved_employee_ids employee_aat;
18
19 denied_employee_ids employee_aat;
20 denied_salaries salary_aat;
21 denied_hire_dates hire_date_aat;
22
23 PROCEDURE retrieve_employee_info
24 IS
25 BEGIN
26 SELECT employee_id, salary, hire_date
27 BULK COLLECT INTO employee_ids, salaries, hire_dates
28 FROM employee
29 WHERE department_id = dept_in;
30 END;
31
32 PROCEDURE partition_by_eligibility
33 IS
34 BEGIN
35 FOR indx IN employee_ids.FIRST .. employee_ids.LAST
36 LOOP
37 IF comp_analysis.is_eligible (employee_ids (indx))
38 THEN
39 approved_employee_ids (indx) := employee_ids (indx);
40 ELSE
41 denied_employee_ids (indx) := employee_ids (indx);
42 denied_salaries (indx) := salaries (indx);
43 denied_hire_dates (indx) := hire_dates (indx);
44 END IF;
45 END LOOP;
46 END;
47
48 PROCEDURE add_to_history
49 IS
50 BEGIN
51 FORALL indx IN denied_employee_ids.FIRST .. denied_employee_ids.LAST
52 INSERT INTO employee_history
53 (employee_id
54 , salary
55 , hire_date, activity
56 )
57 VALUES (denied_employee_ids (indx)
58 , denied_salaries (indx)
59 , denied_hire_dates (indx), 'RAISE DENIED'
60 );
61 END;
62
63 PROCEDURE give_the_raise
64 IS
65 BEGIN
66 FORALL indx IN approved_employee_ids.FIRST .. approved_employee_ids.LAST
67 UPDATE employee
68 SET salary = newsal
69 WHERE employee_id = approved_employee_ids (indx);
70 END;
71 BEGIN
72 retrieve_employee_info;
73 partition_by_eligibility;
74 add_to_history;
75 give_the_raise;
76 END give_raises_in_department;

掃一眼清單1 和清單2 就會清楚地認識到:改用集合和批量處理方法將增加程式碼量和複雜性。但是,如果你需要大幅度提升效能,這還是值得
的。下面,我們不看這些程式碼,我們來看一看當使用FORALL時,用什麼來處理CURSOR FOR迴圈內的條件邏輯。
定義集合型別與集合
在清單 2中,宣告段的第一部分(第6行至第11行)定義了幾種不同的集合型別,與我將從員工表檢索出的列相對應。我更喜歡基於employee%
ROWTYPE來宣告一個集合型別,但是FORALL還不支援對某些記錄集合的操作,在這樣的記錄中,我將引用個別欄位。所以,我還必須為員工ID、
薪金和僱用日期分別宣告其各自的集合。
接下來為每一列宣告所需的集合(第13行至第21行)。首先定義與所查詢列相對應的集合(第13行至第15行):

employee_ids employee_aat;
salaries salary_aat;
hire_dates hire_date_aat;

然後我需要一個新的集合,用於存放已被批准加薪的員工的ID(第17行):

approved_employee_ids employee_aat;

最後,我再為每一列宣告一個集合(第19行至第21行),用於記錄沒有加薪資格的員工:

denied_employee_ids employee_aat;
denied_salaries salary_aat;
denied_hire_dates hire_date_aat;
深入瞭解程式碼
資料結構確定後,我們現在跳過該程式的執行部分(第72行至第75行),瞭解如何使用這些集合來加速程序。

retrieve_employee_info;
partition_by_eligibility;
add_to_history;
give_the_raise;

我編寫此程式使用了逐步細化法(也被稱為”自頂向下設計”)。所以執行部分不是很長,也不難理解,只有四行,按名稱對過程中的每一步進
行了描述。首先檢索員工資訊(指定部門的所有員工)。然後進行劃分,將要加薪和不予加薪的員工區分出來。完成之後,我就可以將那些不
予加薪的員工新增至員工歷史表中,對其他員工進行加薪。
以這種方式編寫程式碼使最終結果的可讀性大大增強。因而我可以深入到該程式中對我有意義的任何部分。
有了已宣告的集合,我現在就可以使用BULK COLLECT來檢索員工資訊(第23行至第30行)。這一部分有效地替代了CURSOR FOR迴圈。至此,數
據被載入到集合中。
劃分邏輯(第32行至第46行)要求對剛剛填充的集合中的每一行進行檢查,看其是否符合加薪條件。如果符合,我就將該員工ID從查詢填充的
集合複製到符合條件的員工的集合。如果不符合,則複製該員工ID、薪金和僱用日期,因為這些都需要插入到employee_history表中。
初始資料現在已被分為兩個集合,可以將其分別用作兩個不同的FORALL語句(分別從第51行和第66行開始)的驅動器。我將不合格員工的集合
中的資料批量插入到employee_history(add_to_history)表中,並通過give_the_raise過程,在employee表中批量更新合格員工的資訊。
最後再仔細地看一看add_to_history(第48行至第61行),以此來結束對這個重新編寫的程式的分析。FORALL語句(第51行)包含一個IN子句
,它指定了要用於批量INSERT的行號範圍。在對程式進行第二次重寫的說明中,我將把用於定義範圍的集合稱為”驅動集合”。但在
add_to_history的這一版本中,我簡單地假定: 使用在denied_employee_ids中定義的所有行。在INSERT自身內部,關於不合格員工的三個集
合都會被用到;我將把這些集合稱為”資料集合”。可以看到,驅動集合與資料集合無需匹配。在學習Oracle資料庫10g的新特性時,這是一個關
鍵點。
結果,清單 2 的行數大約是清單 1行數的2倍,但是清單 2 中的程式碼會在要求的時間內執行。在使用Oracle資料庫10g之前,在這種情況下,
我只會對能夠在這一時間內執行程式碼並開始下一個任務這一點感到高興。
不過,有了Oracle資料庫10g中最新版的PL/SQL,現在我就可以在效能、可讀性和程式碼量方面作出更多的改進。
將VALUES OF用於此過程
在Oracle資料庫10g中,可以指定FORALL語句使用的驅動集合中的行的子集。可以使用以下兩種方法之一來定義該子集:

將資料集合中的行號與驅動集合中的行號進行匹配。你需要使用INDICES OF子句。
將資料集合中的行號與驅動集合中所定義行中找到的值進行匹配。這需要使用VALUES OF子句。
在對give_raises_in_department進行第二次和最後一次改寫中我將使用VALUES OF子句。清單 3 包含這個版本的全部程式碼。我將略過這一程式
中與前一版本相同的部分。
從宣告集合開始,請注意我不再另外定義集合來存放合格的和不合格的員工資訊,而是在清單 3 (第17行至第21行)中宣告兩個”引導”集合:
一個用於符合加薪要求的員工,另一個用於不符合加薪要求的員工。這兩個集合的資料型別都是布林型;不久將會看到,這些集合的資料型別
與FORALL語句毫無關係。FORALL語句只關心定義了哪些行。 在員工表中擁有50 000行資訊的give_raises_in_department的三種執行方法的佔
用時間 執行方法 用時
CURSOR FOR迴圈 00:00:38.01
Oracle資料庫10g之前的批量處理 00:00:06.09
Oracle資料庫10g的批量處理 00:00:02.06
在員工表中擁有100,000行資料的give_raises_in_department的三種執行方法的佔用時間 執行方法 用時
CURSOR FOR迴圈 00:00:58.01
Oracle資料庫10g之前的批量處理 00:00:12.00
Oracle資料庫10g的批量處理 00:00:05.05

表1:處理50,000行和100,000行資料的用時測試結果
retrieve_employee_info子程式與前面的相同,但是對資料進行劃分的方式完全不同(第32行至第44行)。我沒有將記錄從一個集合複製到另
一個集合(這個操作相對較慢),而只是確定與員工ID集合中的行號相匹配的相應引導集合中的行(通過為其指定一個TRUE值)。
現在可以在兩個不同FORALL語句(由第49行和第65行開始)中,將approved_list和denied_list集合用作驅動集合。
為了插入到employee_history表中,我使用瞭如下語句:

FORALL indx IN VALUES OF denied_list

為了進行更新(給員工進行加薪),我使用這一格式:

FORALL indx IN VALUES OF approved_list

在這兩個DML語句中,資料集合是在BULK COLLECT 檢索步驟中填充的最初的集合;沒有進行過複製。利用VALUES OF,Oracle資料庫在這些資料
集合的行中進行篩選,僅使用行號與驅動集合中行號相匹配的行
利用本程式中的VALUES OF,可以避免複製對全部記錄進行復制,而是用行號的一個簡單列表來替換它們。對於大型陣列,進行這些複製的開銷
是非常可觀的。為了測試Oracle資料庫10g的優越性,我裝入employee表並對50,000行和100,000行的資料執行測試。為了模擬更多的現實情況
,我將Oracle資料庫10g之前的批量處理的執行方法作了修改以進行集合內容的多次複製。然後我使用SQL*Plus SET TIMING ON來顯示執行各個
不同的執行方法所用的時間。表 1 給出了結果。
從這些時間測定得到的結論非常清楚:由單個DML語句變為批量處理將大幅縮短耗用時間,資料為50,000行時的用時由38秒減為6秒,資料為
100,000行時的用時由58秒減為12秒。而且,通過使用VALUES OF來避免複製資料,我可以將用時縮短一半左右。
即使沒有效能上的改進,VALUES OF及其同類子句–INDICES OF也提高了PL/SQL語言的靈活性,使開發人員能夠更輕鬆地編寫出更直觀和更容易
維護的程式碼。
在產品壽命這一點上,PL/SQL是一種成熟且功能強大的語言。因而,其很多新特性都是逐漸增加和改進而成的。不過,這些新特性還是使應用
程式的效能和開發人員的開發效率有了重大改變。VALUES OF就是這種特性的一個很好的例子。

相關推薦

Oracle資料庫批量操作forallBULK COLLECT

oracle forall FORALL語句的一個關鍵性改進,它可大大簡化程式碼,並且對於那些要在PL/SQL程式中更新很多行資料的程式來說,它可顯著提高其效能。 1: 用FORALL來增強DML的處理能力 Oracle為Oracle8i中的PL/SQL

資料庫批量操作批量更新批量插入)

資料庫的批量操作 為了儘可能提高我們的sql執行效率,一般我們針對多條資料的操作,使用批量更新或者批量插入的方式 方式如下: --批量插入 <insert id="saveUserList" parameterType="java.util.List">

mybatis 對 oracle批量操作

1:返回插入新資料的主鍵(主鍵序列自增) <insert id="saveLanguageType" parameterType="map" flushCache="false"> <selectKey resultType="java.lang.String"

mybatis+mysql/oracle 資料庫批量插入主鍵自增長

1 mybatis+oracle <!-- https://mvnrepository.com/artifact/com.oracle/ojdbc6 --> <dependency> <groupId>com.oracle&

26.PLSQL中使用forallbulk collect批量操作資料庫

        假設有以下資料表emp: 一、使用forall批量修改資料庫        如果有以下需求:要求修改EMPNO為7499、7566、和7654的成員的sal值為5000,如果我們在P

C#對資料庫操作(查詢刪除更新)

查詢: static IQueryable<Outlet> Query(string Region, string DC, string CustomerCode, string KA, string OutletCode, string NestleOutletCode

C#連線Oracle資料庫(直接引用dll使用)查詢資料

    首先,下載Oracle.ManagedDataAccess.dll將其放到自己的專案中,然後引用。程式碼如下:      static void Main(string[] args)     &

Oracle資料庫監聽非常慢基本hang住故障處理

測試人員郵件反饋: 訂購資料庫的連線非常慢,甚至是無法連線,想要我檢檢視看。 經檢視: [email protected]:~/app/admin/wdadb/adump> lsnrctl status LSNRCTL for Linux: Version

oracle資料庫中的timestamp型別轉換為date 型別

0 1引入包 2import oracle.sql.TIMESTAMP;(注意不是import java.sql.Timestamp;) 3//呆段程式碼的意思是,取出上表中某條記錄的Add_time欄位,然後轉換為date型別,然後再由date型別轉換成字串。 Simp

本地客戶端(自己的電腦)連線遠端Oracle資料庫(伺服器端)客戶端安裝步驟

如果本地自己的電腦沒有安裝Oracle(伺服器端資料庫),那就要單獨安裝HA-Instant Client-v11.2.0.3.0.exe(oracle_client客戶端) 如果本地安裝了Oracle(伺服器端資料庫),就包含了HA-Instant Client-v11.2.0.3.0.exe,不需要單獨安

oracle批量操作sql語句

AS oracl har color sql mapper spa ram pen 1.批量刪除/批量更新 mapper: <update id="updatePrjStateByFPrjId" parameterType="string">

Oracle資料庫相關操作

建立表、刪除表、插入資料、更新資料、刪除資料、添加註釋、 修改註釋、修改表名、修改欄位、新增欄位、刪除欄位 1、建立表 CREATE TABLE person( id varchar2(20) primary key, name varchar2(50 char)

ContentProvider提供的對資料庫批量操作的方法和對資料庫變化監控的方法

最近專案中用到了資料批量入庫和監控資料庫變化的需求,整理總結如下: 1.批量操作資料庫的方法 1)ContentProvider中提供了批量處理資料的方法applyBatch,Android原始碼在ContentProvider.java中實現如下: @Override

Python Test API - 用python連線Oracle資料庫操作

目的: 通過python連線遠端的一臺oracle資料庫伺服器,並用python程式碼實現增刪改查的操作。本研究是為測試API準備資料庫環境的第一步。 環境配置(如果用64bit的,就都需要64bit):  1. 在本地計算機上安裝好oralce client (64bit) 2.

程式對關係型資料庫批量操作

一、批量插入 1.1、oracle批量插入 insert into test(name) select * from (select '111' from dual union all select '222' from dualunion all select '333' from dualunion

Oracle資料庫常用操作總結(一)

--oracle cs架構軟體 --客戶端 --tns  --協議 --ip --埠 --資料庫名字 --監聽如果出了問題,先刪除所有監聽,再重建。netca。tns檔案中名字不能重複, --oracle預設自帶兩個管理員使用者 sys system 這兩個使用者在登入時

Java實現oracle資料批量操作

java事務處理 TestDemo:結合位於java.sql下面的介面 PreparedStatement和oracle事務實現批量刪除 常用方法: int [] executeBatch(): 將一批命令提交給資料庫來執行,如果全部命令執行成功,則返回更新計陣列成的陣列。 vo

oracle資料庫批量殺會話

當rac環境中節點一會話佔滿時,我們可以在其他節點批量殺節點一的會話:  select 'alter system kill session '''||sid||','||SERIAL#||',@1'' immediate;' from gv$session where inst_id

資料庫批量操作

DROP PROCEDURE IF EXISTS proc_initData; DELIMITER $ CREATE PROCEDURE proc_initData() BEGIN DECLARE i INT DEFAULT 從哪開始; WHILE i<

oracle資料庫常見操作

--表分析 select 'analyze table '||table_name||' compute statistics;' from user_tables;   --查詢空表 select table_name,num_rows from user_tables