Oracle 11gR2 FORALL SAVE EXCEPTIONS в изглед на база данни не работи

Искам да се възползвам от груповата DML операция на Oracle и обработката на изключения. Моето изискване е да изпълня DML операция на изглед на база данни, където изпълнява някои валидации чрез задействане на изглед, след което накрая вмъквам/актуализирам основната таблица. Въпреки това, FORALL .. SAVE EXCEPTIONS на Oracle изглежда не улавя грешка при валидиране, повдигната в изгледа. Това ли е ограничението/ограничението на SAVE EXCEPTION, където работи само с таблица на базата данни, но не и с преглед? Документацията на Oracle изглежда също не споменава това. По-долу са моите тестови кодове (базирани на модификация от Обработка на изключения при групови операции):

Създаване на таблица:

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

Създайте изглед на масата:

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;
/ 

Тествайте кода на 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 от задействане на изгледа вместо обработка на пълни 100 вмъквания и прихващане на грешки на индекс 50 и 51 за последваща проверка.

Всякакви отзиви за това ще бъдат много оценени!


person Potoroo    schedule 06.10.2014    source източник
comment
Можете ли да извършите едно тестово вмъкване в изгледа и да проверите дали е повдигнато изключение???   -  person psaraj12    schedule 06.10.2014
comment
Да, използвайки моя тестов код по-горе, повдигнатото изключение е ORA-20815 от тригера вместо ORA-24381 от SAVE EXCEPTIONS. Следователно не мога да хвана групово изключения, защото операцията FORALL веднага ще излезе с грешка.   -  person Potoroo    schedule 07.10.2014


Отговори (2)


Забравете за тригера INSTEAD OF точно сега. Нека се съсредоточим върху частта BULK EXCEPTIONS. Моят тестов случай има таблица с ID като NOT NULL колона. Изглед към него. Ще използвам FORALL INSERT и ще се опитам да вмъкна NULL стойности в VIEW чрез индекс 50 и 51 в колекцията. Очаква се да получите EXCEPTION, когато се опитвате да вмъкнете NULL в VIEW:

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, поради което кодът никога не влиза в блока за изключения.

person Lalit Kumar B    schedule 06.10.2014
comment
Оп проверете XXX_UPDATABLE_COLUMNS (XXX може да бъде ALL, USER или DBA) за тази таблица, за да видите дали колоната ID може да се актуализира. Както виждам от този отговор, колоната може да се актуализира, следователно ВМЕСТО Trigger не се изисква docs.oracle.com/cd/E11882_01/appdev.112/e25519/ - person psaraj12; 06.10.2014
comment
Благодаря на всички за отговора. Имам нужда от тригера вместо тук, тъй като трябва да извърша поредица от валидации, преди да вмъкна данни в таблицата. raise_application_error в първоначалния ми пост е само пример, който искам да тествам, за да видя дали SAVE EXCEPTION може да го хване или не. Но изглежда, че SAVE EXCEPTION наистина работи само за грешки, свързани с директни DML операции (?). Опитвам се да потърся документация на Oracle, за да потвърдя предположението си, но все още нямам успех. - person Potoroo; 06.10.2014

Можете ли да опитате нещо като по-долу, където улавяте изключението, повдигнато в ВМЕСТО TRIGGER

  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;
person psaraj12    schedule 07.10.2014
comment
Благодаря, опитах да използвам WHEN OTHERS THEN, грешката е уловена на индекс 50 по време на FORALL INSERT, но след това цикълът просто спира тук, без да продължи с останалата част от INSERTs, дори SAVE EXCEPTIONS е посочено. - person Potoroo; 08.10.2014
comment
Освен това, ако изключение ex_dml_errors -24381 не е повдигнато, тогава няма смисъл да се използва SAVE EXCEPTIONS, тъй като грешките вече не се обработват групово. - person Potoroo; 08.10.2014