2011-06-24 10 views
3

私たちのデータベースには、約4つの他のテーブルからのIDによってレコードが参照されるテーブルがあります。これらの '子'表には、 'マスター表'への外部キーがあり、 'on delete set null'があります。すべてのテーブルにはmutating-tablesシステムがあります(つまり、plsql-tableでパッケージ化し、プロシージャがafter文トリガから呼び出されたときにレコードを処理します)。 しかし、マスターテーブルでレコードを削除すると、子レコードは「テーブルが突然変異しています」エラーを返します。外部キーが暗黙の更新ステートメントを起動するように見えるので、これは奇妙なものです。これはplsqlテーブルにあります。削除時に外部キーでテーブルを変更するとエラーが発生する

私は後でこれがなぜ、私はいくつかの関連情報を浚渫しているように見えることはできませんしようとしている! 確かに、関連するテーブルの参照されたidフィールドを、マスタのafter-statementトリガからnullに設定するだけで解決することができますが、なぜこのようなことが起こっているのかは分かります。

コードエラーを再現する:

CREATE TABLE master_table (ID NUMBER(5) NOT NULL); 
CREATE TABLE child_table (ID NUMBER(5) NOT NULL, master_id NUMBER(5)); 

alter table master_table add constraint master_pk primary key (ID); 

alter table child_table add constraint child_pk primary key (ID); 

ALTER TABLE child_table 
    add constraint on_delete_master foreign key (master_id) 
    references master_table (ID) on delete set null; 

CREATE OR REPLACE PACKAGE pkg_child 
IS 
PROCEDURE init_temp; 
PROCEDURE add_temp(i_action IN VARCHAR2, 
        i_master_old IN child_table.master_id%TYPE, 
        i_master_new IN child_table.master_id%TYPE); 
PROCEDURE process_temp; 
END; 
/
CREATE OR REPLACE PACKAGE BODY pkg_child IS 
    TYPE temp_record IS RECORD(
     action  VARCHAR2(1), 
     old_master_id child_table.master_id%TYPE, 
     new_master_id child_table.master_id%TYPE); 

    TYPE type_temp IS TABLE OF temp_record INDEX BY BINARY_INTEGER; 

    tab_temp type_temp; 

    PROCEDURE init_temp IS 
    BEGIN 
     tab_temp.delete; 
    END; 

    PROCEDURE add_temp(i_action  IN VARCHAR2, 
         i_master_old IN child_table.master_id%TYPE, 
         i_master_new IN child_table.master_id%TYPE) IS 
     v_id BINARY_INTEGER; 
    BEGIN 
     v_id := nvl(tab_temp.last, 0) + 1; 
     tab_temp(v_id).action := i_action; 
     tab_temp(v_id).old_master_id := i_master_old; 
     tab_temp(v_id).new_master_id := i_master_new; 
    END; 

    PROCEDURE process_temp IS 
     v_id BINARY_INTEGER; 
     v_total NUMBER; 
    BEGIN 
     v_id := tab_temp.first; 
     WHILE v_id IS NOT NULL LOOP 
     IF tab_temp(v_id).action = 'U' THEN 
      SELECT COUNT(1) 
       INTO v_total 
       FROM child_table; 
     END IF; 
     v_id := tab_temp.next(v_id); 
     END LOOP; 
    END; 
END; 
/
CREATE OR REPLACE TRIGGER child_table_bs 
BEFORE 
INSERT OR UPDATE OR DELETE 
ON child_table 
REFERENCING OLD AS OLD NEW AS NEW 
BEGIN 
    pkg_child.init_temp; 
END; 
/
CREATE OR REPLACE TRIGGER child_table_ar 
AFTER 
INSERT OR DELETE OR UPDATE 
ON child_table 
REFERENCING OLD AS OLD NEW AS NEW 
FOR EACH ROW 
DECLARE 
    v_action VARCHAR2(1); 
BEGIN 
    IF inserting THEN 
     v_action := 'I'; 
    ELSIF updating THEN 
     v_action := 'U'; 
    ELSIF deleting THEN 
     v_action := 'D'; 
    END IF; 
    pkg_child.add_temp(v_action, :old.id, :new.id); 
END; 
/
CREATE OR REPLACE TRIGGER child_table_as 
AFTER 
INSERT OR UPDATE OR DELETE 
ON child_table 
REFERENCING OLD AS OLD NEW AS NEW 
BEGIN 
pkg_child.process_temp; 
END; 
/

INSERT ALL 
    INTO master_table (id) VALUES (1) 
    INTO master_table (id) VALUES (2) 
    INTO master_table (id) VALUES (3) 
    INTO master_table (id) VALUES (4) 
SELECT * FROM dual; 

INSERT ALL 
    INTO child_table (id, master_id) VALUES (1, NULL) 
    INTO child_table (id, master_id) VALUES (2, 1) 
    INTO child_table (id, master_id) VALUES (3, 2) 
    INTO child_table (id, master_id) VALUES (4, NULL) 
SELECT * FROM dual; 

-- error on this delete: mutating tables 
-- why? 
DELETE FROM master_table 
WHERE id = 2; 

クリーンアップコード:

DROP TRIGGER child_table_bs; 
DROP TRIGGER child_table_ar; 
DROP TRIGGER child_table_as; 
DROP PACKAGE pkg_child; 
DROP TABLE child_table; 
DROP TABLE master_table; 

おかげ

答えて

0

あなたは1文で、複数の行に影響を与える可能性があり、マスターテーブルからDELETEを持っています。 CASCADE制約のため、削除された各行は、CHILDテーブルの暗黙的/再帰的UPDATEステートメントを起動します。つまり、理論上、子テーブルの複数のUPDATEを持つことができます。

は、2つのUPDATE child_table文を生成するDELETE FROM master_table WHERE id in (1, 2)

をしたと言います。これらはそれぞれ、あなたは、単一のDELETE文の下の任意のSELECTの結果は、特定の時点で一貫している必要があり

SELECT COUNT(1) 
INTO v_total 
FROM child_table 

の2つの実行を取得したいので、AFTERのUPDATEトリガを実行しようとするだろう。しかし、SELECTはDELETEの最後には発生しませんが、削除の間に複数回実行されます。そのたびに、結果が異なる可能性があります。 Oracleはあなたが望む/期待している結果を実現することができます。したがって、変換表エラーがスローされます。

ビジネス要件を知らなければ、解決策を推薦することは難しいです。オラクルと同様、私たちはあなたが何をしようとしているのかもわかりません。場合によっては、トランザクションの最後に実行されるON-COMMIT MVまたはDBMS_JOBによって解決される可能性があります。

+0

プロセスの中で何が起こるかは、選択、更新、プロシージャ/ファンクションのすべてです。 mutテーブルは、最初の選択で発生するので、私はそれを例として取り上げました。だから、もし私が正しく理解すると、Oracleは私にエラーを通知します。なぜなら、制約のトリガのために起こる更新/削除の量を判断できないからです。もし私がFKを落として、代わりにマスターの削除の後ろのステートメントから子を更新すると、それはうまくいくでしょう。 – Tom

関連する問題