insert all 語句 用法詳解

INSERT ALL語句的一些使用技巧。


首先描述一下需求,其實要進行的操做很簡單:從一張表取數據插入到另外一張表中,插入的目標表作了一個應用系統級的日誌表,也就是說在插入目標表的同時,還須要將相同的數據插入到日誌表中。 併發

這個操做其實並不複雜,可是想找到一個效率最高、併發性最好的方法並不容易。 函數

最普通的方法是兩個INSERT INTO SELECT語句。這種方法編碼最簡單,可是存在着兩次插入的數據不一致的狀況。若是要解決這個問題,必須經過人爲加鎖的方式,這樣又會影響併發性。 測試

還有一種方式是經過臨時表的方式。第一次將數據放到臨時表中,而後經過臨時表把數據分別插入目標表和日誌表。這種方法雖然解決了併發性問題,可是效率比較低。相同的數據須要查詢三次,插入三次。 編碼

PL/SQL的語法RETURNING語句其實很適合這種狀況,惋惜的是RETURNING語句只支持INSERT INTO VALUES語句,不支持INSERT INTO SELECT語句。 日誌

若是數據量不大的話,還能夠考慮使用SELECT BULK COLLECT INTO和FOR ALL INSERT語句配合。若是數據量比較大的話,能夠考慮在上面的基礎上加上LIMIT語句限制一次處理的數據量大小。這種方法不但解決了併發性並且只須要讀取一次插入兩次,執行效率相對比較高。惟一的缺點是,須要將數據放到內存的變量中,不但須要額外的內存空間,並且這種數據在內存中的中轉必然要比數據從源表直接插入到目標表效率要低一些。並且這種方法須要的編碼量相對較大。 內存

最後想到了使用INSERT ALL語法。INSERT ALL語法是9i的新功能,使用INSERT ALL語法能夠說是解決這個方法的最佳途徑了,只須要讀取一次,就能夠完成兩次插入,沒有併發性問題,不須要額外的存儲空間,編碼簡單,只須要一條SQL語句就能夠搞定。 文檔

從上面種種方面看,INSERT ALL語句簡直就是這個問題的完美解決方案,可是問題纔剛剛開始。 get

首先,碰到的第一個問題就是,INSERT ALL的子查詢中不支持序列。而在將源表數據插入到目標表的過程當中須要使用序列來構造ID。 it

不過這個問題被咱們經過創建函數的方法繞過去了。 io

下面這個例子簡單描述了這種狀況:

SQL> CREATE TABLE A (ID NUMBER, NAME VARCHAR2(30));

表已建立。

SQL> CREATE TABLE LOG_A (ID NUMBER, NAME VARCHAR2(30));

表已建立。

SQL> CREATE SEQUENCE SEQ_TEST;

序列已建立。

SQL> INSERT ALL INTO A (ID, NAME) VALUES (ID, TNAME) 
2 INTO LOG_A (ID, NAME) VALUES (ID, TNAME) 
3 SELECT SEQ_TEST.NEXTVAL ID, TNAME FROM TAB;
SELECT SEQ_TEST.NEXTVAL ID, TNAME FROM TAB
*第 3 行出現錯誤:
ORA-02287: 此處不容許序號

Oracle的文檔上也明確描述了不能在子查詢中使用序列,可是經過測試發現,若是將序列封裝在函數中是能夠騙過Oracle的。

SQL> CREATE OR REPLACE FUNCTION F_GETSEQ RETURN NUMBER AS
2 V_SEQ NUMBER;
3 BEGIN
4 SELECT SEQ_TEST.NEXTVAL INTO V_SEQ FROM DUAL;
5 RETURN V_SEQ;
6 END;
7 /

函數已建立。

SQL> INSERT ALL INTO A (ID, NAME) VALUES (ID, TNAME) 
2 INTO LOG_A (ID, NAME) VALUES (ID, TNAME) 
3 SELECT F_GETSEQ ID, TNAME FROM TAB;

已建立48行。

問題彷佛解決了,可是更大的問題出現了,觀察A表和LOG_A表發現竟然獲得的結果是不同的:

SQL> SELECT * FROM A;

ID NAME
---------- ------------------------------
1 DEPT
3 EMP
5 BONUS
7 SALGRADE
9 DUMMY
11 TEST
13 DOCS
15 DR$MYINDEX$I
17 DR$MYINDEX$K
19 DR$MYINDEX$R
21 DR$MYINDEX$N
23 TEST_CLOB
25 FACT
27 MLOG$_DIM_A
29 MLOG$_DIM_B
31 MLOG$_FACT
33 MV_FACT
35 MLOG$_MV_FACT
37 RUPD$_MV_FACT
39 A
41 LOG_A
43 TEST_TAB
45 DIM_A
47 DIM_B

已選擇24行。

SQL> SELECT * FROM LOG_A;

ID NAME
---------- ------------------------------
2 DEPT
4 EMP
6 BONUS
8 SALGRADE
10 DUMMY
12 TEST
14 DOCS
16 DR$MYINDEX$I
18 DR$MYINDEX$K
20 DR$MYINDEX$R
22 DR$MYINDEX$N
24 TEST_CLOB
26 FACT
28 MLOG$_DIM_A
30 MLOG$_DIM_B
32 MLOG$_FACT
34 MV_FACT
36 MLOG$_MV_FACT
38 RUPD$_MV_FACT
40 A
42 LOG_A
44 TEST_TAB
46 DIM_A
48 DIM_B

已選擇24行。

SQL> ROLLBACK;

回退已完成。

感受上Oracle竟然彷佛對源表進行了兩次查詢。可是從數據的分佈狀況上看又不像。我的感受Oracle對於每條記錄彷佛是將取序列的函數執行了兩次。

操做流程相似於

FOR ALL ROWID IN TAB LOOP

SELECT TNAME FROM TAB WHERE ROWID =:1;

INSERT INTO A (F_GETSEQ, TNAME);

INSERT INTO LOG_A (F_GETSEQ, TNAME);

END LOOP;

而同事又有了另外一個發現,當包含了ROWNUM列時,獲得的結果是正確的:

SQL> INSERT ALL INTO A (ID, NAME) VALUES (ID, TNAME) 
2 INTO LOG_A (ID, NAME) VALUES (ID, TNAME) 
3 SELECT ROWNUM RN, F_GETSEQ ID, TNAME FROM TAB;

已建立48行。

SQL> SELECT * FROM A;

ID NAME
---------- ------------------------------
49 DEPT
50 EMP
51 BONUS
52 SALGRADE
53 DUMMY
54 TEST
55 DOCS
56 DR$MYINDEX$I
57 DR$MYINDEX$K
58 DR$MYINDEX$R
59 DR$MYINDEX$N
60 TEST_CLOB
61 FACT
62 MLOG$_DIM_A
63 MLOG$_DIM_B
64 MLOG$_FACT
65 MV_FACT
66 MLOG$_MV_FACT
67 RUPD$_MV_FACT
68 A
69 LOG_A
70 TEST_TAB
71 DIM_A
72 DIM_B

已選擇24行。

SQL> SELECT * FROM LOG_A;

ID NAME
---------- ------------------------------
49 DEPT
50 EMP
51 BONUS
52 SALGRADE
53 DUMMY
54 TEST
55 DOCS
56 DR$MYINDEX$I
57 DR$MYINDEX$K
58 DR$MYINDEX$R
59 DR$MYINDEX$N
60 TEST_CLOB
61 FACT
62 MLOG$_DIM_A
63 MLOG$_DIM_B
64 MLOG$_FACT
65 MV_FACT
66 MLOG$_MV_FACT
67 RUPD$_MV_FACT
68 A
69 LOG_A
70 TEST_TAB
71 DIM_A
72 DIM_B

已選擇24行。

SQL> ROLLBACK;

回退已完成。

此次執行的結果是正確的。Tom在他的書中描述過ROWNUM的肯定結果集的功能,也就是說受到ROWNUM的影響,ORACLE將處理流程變成了

FOR ALL ROWID IN TAB LOOP

SELECT ROWNUM RN, F_GETSEQ ID, TNAME FROM TAB WHERE ROWID =:1;

INSERT INTO A (ID, TNAME);

INSERT INTO LOG_A (ID, TNAME);

END LOOP;

因爲存在ROWNUM,Oracle在執行查詢的時候就運行了F_GETSEQ函數,所以F_GETSET函數對於每條記錄只在查詢的時候執行一次。

若是將函數改寫一下,將ROWNUM做爲輸入參數,同樣能夠解決這個問題。

SQL> CREATE OR REPLACE FUNCTION F_GETSEQ (P_IN IN NUMBER) RETURN NUMBER AS
2 V_SEQ NUMBER;
3 BEGIN
4 SELECT SEQ_TEST.NEXTVAL INTO V_SEQ FROM DUAL;
5 RETURN V_SEQ;
6 END;
7 /

函數已建立。

SQL> INSERT ALL INTO A (ID, NAME) VALUES (ID, TNAME) 
2 INTO LOG_A (ID, NAME) VALUES (ID, TNAME) 
3 SELECT F_GETSEQ(ROWNUM) ID, TNAME FROM TAB;

已建立48行。

SQL> SELECT * FROM A WHERE ROWNUM < 5;

ID NAME
---------- ------------------------------
73 DEPT
74 EMP
75 BONUS
76 SALGRADE

SQL> SELECT * FROM LOG_A WHERE ROWNUM < 5;

ID NAME
---------- ------------------------------
73 DEPT
74 EMP
75 BONUS
76 SALGRADE

SQL> ROLLBACK;

回退已完成。

除了上面描述的方法,若是是Oracle10g的話,還能夠創建一個DETERMINISTIC的函數。在10g中Oracle徹底信任DETERMINISTIC聲明,對於相同的輸入,會採用相同的輸出,而不去真正的執行函數。

例如,在9i下執行:

SQL> CREATE OR REPLACE FUNCTION F_GETSEQ RETURN NUMBER DETERMINISTIC AS
2 V_SEQ NUMBER;
3 BEGIN
4 SELECT SEQ_TEST.NEXTVAL INTO V_SEQ FROM DUAL;
5 RETURN V_SEQ;
6 END;
7 /

Function created.

SQL> SELECT F_GETSEQ FROM TAB;

F_GETSEQ
----------
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

25 rows selected.

SQL> SELECT * FROM V$VERSION;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production PL/SQL Release 9.2.0.4.0 - Production
CORE 9.2.0.3.0 Production
TNS for Linux: Version 9.2.0.4.0 - Production
NLSRTL Version 9.2.0.4.0 - Production

而10g中,上面的查詢變成了:

SQL> CREATE OR REPLACE FUNCTION F_GETSEQ RETURN NUMBER DETERMINISTIC AS
2 V_SEQ NUMBER;
3 BEGIN
4 SELECT SEQ_TEST.NEXTVAL INTO V_SEQ FROM DUAL;
5 RETURN V_SEQ;
6 END;
7 /

函數已建立。

SQL> SELECT F_GETSEQ FROM TAB;

F_GETSEQ
----------
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97
97

已選擇24行。

SQL> SELECT * FROM V$VERSION;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production

所以,在10g中還能夠經過創建一個DETERMINISTIC屬性的函數來解決這個問題,在函數調用過程當中輸入主鍵或者ROWID來惟一標識每條記錄。因爲TAB是系統視圖,不包含ROWID信息,須要創建一張新表:

SQL> CREATE TABLE TEST_TAB AS SELECT * FROM TAB;

表已建立。

SQL> CREATE OR REPLACE FUNCTION F_GETSEQ(P_ID IN ROWID) RETURN NUMBER DETERMINISTIC AS
2 V_SEQ NUMBER;
3 BEGIN
4 SELECT SEQ_TEST.NEXTVAL INTO V_SEQ FROM DUAL;
5 RETURN V_SEQ;
6 END;
7 /

函數已建立。

SQL> INSERT ALL INTO A (ID, NAME) VALUES (ID, TNAME) 
2 INTO LOG_A (ID, NAME) VALUES (ID, TNAME) 
3 SELECT F_GETSEQ(ROWID) ID, TNAME FROM TEST_TAB;

已建立48行。

SQL> SELECT * FROM A WHERE ROWNUM < 5;

ID NAME
---------- ------------------------------
98 DEPT
99 EMP
100 BONUS
101 SALGRADE

SQL> SELECT * FROM LOG_A WHERE ROWNUM < 5;

ID NAME
---------- ------------------------------
98 DEPT
99 EMP
100 BONUS
101 SALGRADE

至此,這個數據插入問題已經所有解決了。

相關文章
相關標籤/搜索