2014-10-06 67 views
0

我想利用Oracle批量DML操作和異常處理。我的要求是在數據庫視圖上執行DML操作,通過視圖觸發器執行一些驗證,然後插入/更新基礎表。但是,Oracle的FORALL .. SAVE EXCEPTIONS似乎並未捕獲在視圖中引發的驗證錯誤。這是SAVE EXCEPTION的限制/限制,它只適用於數據庫表而不是查看? Oracle文檔似乎也沒有提到這一點。下面是我的測試代碼(基於改進自Handling Exceptions in Bulk Operations):Oracle 11gR2在數據庫視圖上FORALL SAVE EXCEPTIONS不起作用

創建表:

create or replace view exception_test_v as 
select exception_test.id id 
    ,sysdate daytime 
from exception_test; 

對視圖創建觸發器:

create or replace trigger iud_exception_test 
    instead of insert or update or delete on exception_test_v 
    for each row 
declare 

begin 
    if inserting then 

     if nvl(:new.id, 0) = 0 then 
      RAISE_APPLICATION_ERROR(-20815, 'ID must not be null!'); 
     end if; 

     insert into exception_test (id) values (:new.id); 

    end if;  

    end; 
/

create table exception_test (
    id number(10) not null 
); 

在表上創建視圖

數據庫視圖上DML的測試代碼:

declare 
    TYPE t_tab IS TABLE OF exception_test_v%ROWTYPE; 

    l_tab   t_tab := t_tab(); 
    l_error_count NUMBER; 

    ex_dml_errors EXCEPTION; 
    PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381); 
BEGIN 
    -- Fill the collection. 
    FOR i IN 1 .. 100 LOOP 
    l_tab.extend; 
    l_tab(l_tab.last).id := i; 
    END LOOP; 

    -- Cause a failure. 
    l_tab(50).id := NULL; 
    l_tab(51).id := NULL; 

    EXECUTE IMMEDIATE 'TRUNCATE TABLE exception_test'; 

    -- Perform a bulk operation. 
    BEGIN 
    FORALL i IN l_tab.first .. l_tab.last SAVE EXCEPTIONS 
     INSERT INTO exception_test_v (id) 
     VALUES (l_tab(i).id); 
    EXCEPTION 
    WHEN ex_dml_errors THEN 
     l_error_count := SQL%BULK_EXCEPTIONS.count; 
     DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count); 
     FOR i IN 1 .. l_error_count LOOP 
     DBMS_OUTPUT.put_line('Error: ' || i || 
      ' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index || 
      ' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); 
     END LOOP; 
    END; 
END; 

測試代碼在視圖觸發器的索引50上出錯,而​​不是在索引50和51上處理完整的100插入和捕獲錯誤以進行後期檢查。

對此的任何反饋將不勝感激!

+0

可以執行單一測試插入視圖,並檢查是否異常升高引發的異常??? – psaraj12 2014-10-06 07:52:39

+0

是的,使用我上面的測試代碼,引發的異常是來自觸發器的'ORA-20815',而不是'SAVE EXCEPTIONS'中的'ORA-24381'。因此我無法批量處理異常情況,因爲'FORALL'操作會立即出錯。 – Potoroo 2014-10-07 07:31:00

回答

1

現在忘掉INSTEAD OF觸發器。我們來關注BULK EXCEPTIONS部分。我的測試用例有一個ID列表,其列爲NOT NULL列。它的觀點。我將使用FORALL INSERT並嘗試通過集合中的索引50和51將NULL值插入VIEW。期望是試圖在VIEW插入NULL時,爲了獲得EXCEPTION

SQL> create table exception_test (
    2 ID NUMBER(10) NOT NULL 
    3 ); 

Table created. 

SQL> 
SQL> 
SQL> create or replace view exception_test_v as 
    2 select exception_test.id id 
    3  ,SYSDATE DAYTIME 
    4 from exception_test; 

View created. 

SQL> 
SQL> declare 
    2 TYPE t_tab IS TABLE OF exception_test_v%ROWTYPE; 
    3 
    4 l_tab   t_tab := t_tab(); 
    5 l_error_count NUMBER; 
    6 
    7 ex_dml_errors EXCEPTION; 
    8 PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381); 
    9 BEGIN 
10 -- Fill the collection. 
11 FOR i IN 1 .. 100 LOOP 
12  l_tab.extend; 
13  l_tab(l_tab.last).id := i; 
14 END LOOP; 
15 
16 -- Cause a failure. 
17 l_tab(50).id := NULL; 
18 l_tab(51).id := NULL; 
19 
20 EXECUTE IMMEDIATE 'TRUNCATE TABLE exception_test'; 
21 
22 -- Perform a bulk operation. 
23 BEGIN 
24  FORALL I IN 1 .. L_TAB.COUNT SAVE EXCEPTIONS 
25  INSERT INTO exception_test_v (id) 
26  VALUES (L_TAB(I).ID); 
27 EXCEPTION 
28  WHEN EX_DML_ERRORS THEN 
29  dbms_output.put_line('Inside exception'); 
30  l_error_count := SQL%BULK_EXCEPTIONS.count; 
31  DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count); 
32  FOR I IN 1 .. L_ERROR_COUNT LOOP 
33   DBMS_OUTPUT.put_line('Error: ' || i || 
34   ' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index || 
35   ' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); 
36  END LOOP; 
37 END; 
38 END; 
39/
Inside exception 
Number of failures: 2 
Error: 1 Array Index: 50 Message: ORA-01400: cannot insert NULL into() 
Error: 2 Array Index: 51 Message: ORA-01400: cannot insert NULL into() 

PL/SQL procedure successfully completed. 

SQL> 
SQL> select count(*) from exception_test; 

    COUNT(*) 
---------- 
     98 

所以,你看到SAVE EXCEPTIONS有兩個錯誤。

你的測試用例的問題是,代碼永遠不會進入EXCEPTION塊。您可以嘗試刪除RAISE_APPLICATION_ERROR並查看。 PL/SQL塊將正常執行。由於觸發事件而產生的錯誤是而不是24381,因此代碼永遠不會進入異常塊。

+0

對此表進行操作檢查XXX_UPDATABLE_COLUMNS(XXX可以是ALL,USER或DBA),以查看ID列是否可更新。從此答案可以看出該列是可更新的,因此INSTEAD OF觸發器不是必需的http://docs.oracle .com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS20041 – psaraj12 2014-10-06 08:40:43

+0

謝謝大家的回覆。我需要這裏的而不是觸發器,因爲我需要在將數據插入表中之前執行一系列驗證。我原始文章中的'raise_application_error'只是一個我想測試看到的樣本是'SAVE EXCEPTION'可以捕捉或不捕捉。但似乎'SAVE EXCEPTION'實際上只適用於直接與DML操作相關的錯誤(?)。我試圖尋找Oracle文檔來確認我的假設,但仍然沒有運氣。 – Potoroo 2014-10-06 11:45:14

0

你可以試試下面有點像,你趕上TRIGGER

INSTEAD OF
declare 
    TYPE t_tab IS TABLE OF exception_test_v%ROWTYPE; 

    l_tab   t_tab := t_tab(); 
    l_error_count NUMBER; 

    ex_dml_errors EXCEPTION; 
    ex_trigger_errors EXCEPTION; 
    PRAGMA EXCEPTION_INIT(ex_trigger_errors, -20815); 
    PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381); 
    BEGIN 
    -- Fill the collection. 
    FOR i IN 1 .. 100 LOOP 
     l_tab.extend; 
     l_tab(l_tab.last).id := i; 
    END LOOP; 

    -- Cause a failure. 
    l_tab(50).id := NULL; 
    l_tab(51).id := NULL; 

    EXECUTE IMMEDIATE 'TRUNCATE TABLE exception_test'; 

    -- Perform a bulk operation. 
    BEGIN 
     FORALL i IN l_tab.first .. l_tab.last SAVE EXCEPTIONS 
     INSERT INTO exception_test_v (id) 
     VALUES (l_tab(i).id); 
    EXCEPTION 
     WHEN ex_dml_errors THEN 
     l_error_count := SQL%BULK_EXCEPTIONS.count; 
     DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count); 
     FOR i IN 1 .. l_error_count LOOP 
      DBMS_OUTPUT.put_line('Error: ' || i || 
      ' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index || 
      ' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); 
     END LOOP; 
     WHEN ex_trigger_errors THEN 
     l_error_count := SQL%BULK_EXCEPTIONS.count; 
     DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count); 
     FOR i IN 1 .. l_error_count LOOP 
      DBMS_OUTPUT.put_line('Error: ' || i || 
      ' Array Index captured in instead of trigger: ' || SQL%BULK_EXCEPTIONS(i).error_index || 
      ' Message captured in instead of trigger: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE)); 
     END LOOP; 
    END; 
    END; 
+0

謝謝,我曾嘗試過使用'WHEN OTHERS',在'FORALL INSERT'過程中錯誤在索引50上被捕獲,但是這個循環只是在這裏停止而沒有繼續執行其他INSERTs,甚至指定了'SAVE EXCEPTIONS' 。 – Potoroo 2014-10-08 05:10:29

+0

此外,如果沒有引發異常'ex_dml_errors -24381',那麼使用'SAVE EXCEPTIONS'沒有意義,因爲錯誤不再是批量處理。 – Potoroo 2014-10-08 05:11:58