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