ORA-04091 (таблица мутаций), только если заполнено определенное поле - PullRequest
0 голосов
/ 10 июля 2019

Получил стол, на котором есть ключ:

+----+-----------+-----------+
+ ID + ID_PARENT + IS_PARENT +
+----+-----------+-----------+
+  1 +    (null) +         0 +
+  2 +    (null) +         1 +
+  3 +         2 +         0 +
+----+-----------+-----------+

Как видите, идентификатор 1 сам по себе, 3 является потомком 2.

Теперь я хочу иметь триггер, который на INSERT / UPDATE ...

  • ошибки, если вставленная строка является собственным родителем (невозможно)
  • , если у него есть ID_PARENT, установите для IS_PARENT родителя значение 1

Это мой подход:

CREATE OR REPLACE TRIGGER tri_table_set_parent
    BEFORE
    INSERT OR UPDATE ON table
    FOR EACH ROW
    WHEN ( new.id_parent IS NOT NULL )
BEGIN
    IF :new.id = :new.id_parent
    THEN
        RAISE_APPLICATION_ERROR(-20666, 'A gap cant be the parent of itself. More information here: https://youtu.be/hqRZFWE1X_A');
    END IF;
    UPDATE table
    SET is_parent = 1
    WHERE id = :new.id_parent;
END;
/

Ошибка работает так, как задумано, уууу! Но сейчас при вставке у меня проблемы.

При вставке строки без ID_PARENT это работает (потому что триггер не сработает вообще).

Вставить строку, родитель которой ID_PARENT = (null):

INSERT INTO table (ID, ID_PARENT) VALUES (4, 1);

-> Работает!

Но вставив строку, родитель которой получил ID_PARENT:

INSERT INTO table (ID, ID_PARENT) VALUES (5, 3);

-> Ошибки:

ORA-04091: Tabelle TABLE wird gerade geändert, Trigger/Funktion sieht dies möglicherweise nicht
ORA-06512: in "TRI_TABLE_SET_PARENT", Zeile 6
ORA-04088: Fehler bei der Ausführung von Trigger "TRI_TABLE_SET_PARENT"
ORA-06512: in "TRI_TABLE_SET_PARENT", Zeile 6
ORA-04088: Fehler bei der Ausführung von Trigger "TRI_TABLE_SET_PARENT"

Обновление таблицы вообще не работает, та же ошибка.

Хорошо, я понимаю, что не могу выбрать то, что может быть изменено одновременно. Но я обновляю, а также проверяю, что я не ссылаюсь на те же строки.

Так чего мне не хватает?

Ответы [ 4 ]

2 голосов
/ 10 июля 2019

зло в реляционной базе данных - это избыточность , которую вы пытаетесь ввести.

Более правильный реляционный подход заключается в определениитаблицы без столбца IS_PARENT.

select * from my_parent order by id;
        ID  ID_PARENT
---------- ----------
         1           
         2           
         3          2

... и добавьте лишний столбец в доступе к представлению

create view V_MY_PARENT  as
select a.ID, a.ID_PARENT,
case when exists (select null from my_parent where ID_PARENT = a.ID) then 1 else 0 end as IS_PARENT
from my_parent a
order by a.ID;

Чтобы получить все не родители вы получаете доступ к представлению

select * from V_MY_PARENT
where is_parent = 0;

        ID  ID_PARENT  IS_PARENT
---------- ---------- ----------
         1                     0
         3          2          0

Если вы хотите материализовать избыточность (например, из соображений производительности), используйте MATERIALIZED VIEW s.

СПри таком подходе вы не закончите с родителями, классифицированными как не имеющие родителей или наоборот, что вполне возможно в вашем дизайне.

1 голос
/ 10 июля 2019

ORA-04091 (таблица мутирует, триггер может ее не видеть) происходит здесь, потому что у вас есть триггер BEFORE, определенный для «TABLE», а в теле триггера вы пытаетесь обновить «TABLE». Oracle не допускает этого, поскольку это может привести к циклу триггера (т. Е. Если бы это было разрешено, ваша программа могла бы выполнить оператор, который вызывает срабатывание триггера; в теле триггера выполняется оператор, который вызывает запуск триггера; тело триггера, который выполняется оператором, который вызывает срабатывание триггера; внутри тела триггера выполняется оператор, который вызывает срабатывание триггера; в теле триггера выполняется оператор, который вызывает срабатывание триггера и т. д.). Таким образом, вам не разрешено делать это перед триггером. Самое простое решение - изменить триггер на ПОСЛЕ триггера. Другими словами, измените BEFORE INSERT OR UPDATE ON table на AFTER INSERT OR UPDATE ON table. В данном конкретном случае не кажется, что это будет проблемой, но я не знаю, какие ограничения у вас есть на вашем столе, которые могут сделать это неприемлемым. Попробуйте.

1 голос
/ 10 июля 2019

Причина ошибки в том, что вы обновляете строку, которую вы еще не вставили.

Вы можете обновить данные, которые вы вставляете, используя ключевое слово :new.

, поэтому решение заменит оператор обновления на

:new.is_parent := 1
1 голос
/ 10 июля 2019

Просто замените

UPDATE "table"
   SET id_parent = 1
 WHERE id = :new.id_parent;

на

if :old.id = :new.id_parent and updating then
   :new.id_parent := 1;
end if;

, например, избегайте использования DML в той же самой "таблице" против ORA-04091.

PS равняется table в качестве зарезервированного ключевого слова я заменил на "table".

...