Vannak bizonyos megszorítások arra vonatkozóan, hogy egy trigger törzsében mely táblák mely oszlopai érhetők el. Egy DML utasítás által éppen változtatott táblát módosítás alatt álló táblának hívunk. A DML utasításhoz rendelt trigger ezen a táblán van definiálva. Az adott táblához a DELETE CASCADE hivatkozási integritási megszorítás által hozzárendelt táblákat is módosítás alatt álló tábláknak tekintjük. Azokat a táblákat, amelyeket egy adott hivatkozási integritási megszorítás ellenőrzéséhez olvasni kell, megszorítással kapcsolt táblának hívjuk.
A trigger törzsében elhelyezett SQL utasítás nem olvashatja és módosíthatja a triggert aktivizáló utasítás által éppen módosítás alatt álló táblákat.
Ezek a megszorítások sor szintű triggerekre mindig érvényesek, utasítás szintűekre csak akkor, ha a trigger egy DELETE CASCADE eredményeként aktivizálódott.
Az INSERT INTO … SELECT utasítás kivételével az INSERT utasítás abban az esetben, ha csak egyetlen sort szúr be, a bővítendő táblát nem tekinti módosítás alatt állónak.
A következő példában egy fát tárolunk hierarchikusan az adatbázisban. Minden elem tartalmazza a szülő elem azonosítóját. A gyökér elemeket tartalmazó sorokban a szulo oszlop értéke NULL.
Példa
CREATE TABLE fa (
id NUMBER PRIMARY KEY,
szulo NUMBER,
adat NUMBER,
CONSTRAINT fa_fk FOREIGN KEY (szulo)
REFERENCES fa(id)
);
INSERT INTO fa VALUES (1, NULL, 10);
INSERT INTO fa VALUES (2, 1, 20);
INSERT INTO fa VALUES (3, 2, 30);
INSERT INTO fa VALUES (4, 2, 40);
INSERT INTO fa VALUES (5, 3, 50);
INSERT INTO fa VALUES (6, 1, 60);
INSERT INTO fa VALUES (7, 1, 70);
INSERT INTO fa VALUES (8, NULL, 80);
INSERT INTO fa VALUES (9, 8, 90);
SELECT LPAD(' ', (LEVEL-1)*3, '| ') || '+--'
|| '(' || id || ', ' || adat || ')' AS elem
FROM fa
CONNECT BY PRIOR id = szulo
START WITH szulo IS NULL;
/*
ELEM
---------------------
+--(1, 10)
| +--(2, 20)
| | +--(3, 30)
| | | +--(5, 50)
| | +--(4, 40)
| +--(6, 60)
| +--(7, 70)
+--(8, 80)
| +--(9, 90)
9 sor kijelölve.
*/
Készítsünk triggert, ami lehetővé teszi DML segítségével elemek törlését a fából úgy, hogy minden törölt elem gyerekeinek szülőjét átállítja a törölt elem szülőjére. Ha a DML azt a szülőt is törli, akkor annak a szülőjére stb.
A következő trigger logikus megoldás lenne, használata mégsem megengedett, mert egy módosítás alatt álló táblát nem módosíthatunk.
CREATE OR REPLACE TRIGGER tr_fa
BEFORE DELETE ON fa
FOR EACH ROW
BEGIN
/* Ezt kellene csinálni, ha lehetne: */
UPDATE fa SET szulo = :OLD.szulo
WHERE szulo = :OLD.id;
END;
/
show errors
DELETE FROM fa WHERE id = 2;
/*
Hiba a(z) 1. sorban:
DELETE FROM fa WHERE id = 2;
*
Hiba a(z) 1. sorban:
ORA-04091: PLSQL.FA tábla változtatás alatt áll, trigger/funkció számára nem látható
ORA-06512: a(z) "PLSQL.TR_FA", helyen a(z) 3. sornál
ORA-04088: hiba a(z) 'PLSQL.TR_FA' trigger futása közben
*/
/* oracle 12 verzió ORA-02292: integritás megszorítás (PLSQL.FA_FK) megsértés - gyermek rekord található meg */
A megoldás megkerüli ezt a megszorítást:
/* Egy lehetséges megoldás.
Úgy törlünk, hogy az azonosítót -1-re módosítjuk,
triggereken keresztül pedig elvégezzük a tényleges törlést.
Szükségünk van egy temporális táblára,
ezt egy csomagban tároljuk majd.
Első menetben ebben összegyűjtjük a törlendő elemeket.
Második menetben módosítjuk a gyerekeket,
aztán végül elvégezzük a tényleges törlést. */
CREATE OR REPLACE PACKAGE pkg_fa IS
TYPE t_torlendo IS TABLE OF fa%ROWTYPE INDEX BY BINARY_INTEGER;
v_Torlendo t_torlendo;
v_Torles BOOLEAN := FALSE;
/* Megadja egy törlésre kerülő elem végső szülőjét,
hiszen az ő szülőjét is lehet, hogy törlik. */
FUNCTION szulo_torles_utan(p_Id NUMBER) RETURN NUMBER;
END pkg_fa;
/
CREATE OR REPLACE PACKAGE BODY pkg_fa IS
FUNCTION szulo_torles_utan(p_Id NUMBER) RETURN NUMBER IS
v_Id NUMBER := p_Id;
v_Szulo NUMBER;
BEGIN
WHILE v_Torlendo.EXISTS(v_Id) LOOP
v_Id := v_Torlendo(v_Id).szulo;
END LOOP;
RETURN v_Id;
END szulo_torles_utan;
END pkg_fa;
/
-- utasítás szintű BEFORE trigger inicializálja a csomagváltozót
CREATE OR REPLACE TRIGGER tr_fa1
BEFORE UPDATE OF id ON fa
BEGIN
IF NOT pkg_fa.v_Torles THEN
pkg_fa.v_Torlendo.DELETE;
END IF;
END tr_fa1;
/
-- sor szintű trigger tárolja a törlendő elemeket
CREATE OR REPLACE TRIGGER tr_fa
BEFORE UPDATE OF id ON fa
FOR EACH ROW
WHEN (NEW.id = -1)
BEGIN
IF NOT pkg_fa.v_Torles THEN
pkg_fa.v_Torlendo(:OLD.id).id := :OLD.id;
pkg_fa.v_Torlendo(:OLD.id).szulo := :OLD.szulo;
pkg_fa.v_Torlendo(:OLD.id).adat := :OLD.adat;
-- nem módosítunk, hogy az integritás rendben legyen
:NEW.id := :OLD.id;
END IF;
END tr_fa;
/
show errors
-- utasítás szintű AFTER trigger végzi el a munka oroszlánrészét
CREATE OR REPLACE TRIGGER tr_fa2
AFTER UPDATE OF id ON fa
DECLARE
v_Id NUMBER;
BEGIN
IF NOT pkg_fa.v_Torles AND pkg_fa.v_Torlendo.COUNT > 0 THEN
pkg_fa.v_Torles := TRUE;
-- Gyerekek átállítása
v_Id := pkg_fa.v_Torlendo.FIRST;
WHILE v_Id IS NOT NULL LOOP
UPDATE fa SET szulo = pkg_fa.szulo_torles_utan(v_Id)
WHERE szulo = v_Id;
v_Id := pkg_fa.v_Torlendo.NEXT(v_Id);
END LOOP;
-- Törlés
v_Id := pkg_fa.v_Torlendo.FIRST;
WHILE v_Id IS NOT NULL LOOP
DELETE FROM fa
WHERE id = v_Id;
v_Id := pkg_fa.v_Torlendo.NEXT(v_Id);
END LOOP;
pkg_fa.v_Torles := FALSE;
END IF;
END tr_fa2;
/
/*
Emlékeztetőül a fa:
ELEM
---------------------
+--(1, 10)
| +--(2, 20)
| | +--(3, 30)
| | | +--(5, 50)
| | +--(4, 40)
| +--(6, 60)
| +--(7, 70)
+--(8, 80)
| +--(9, 90)
*/
UPDATE fa SET id = -1
WHERE id IN (2, 3, 8);
/*
3 sor módosítva.
*/
SELECT LPAD(' ', (LEVEL-1)*3, '| ') || '+--'
|| '(' || id || ', ' || adat || ')' AS elem
FROM fa
CONNECT BY PRIOR id = szulo
START WITH szulo IS NULL;
/*
ELEM
--------------
+--(1, 10)
| +--(4, 40)
| +--(5, 50)
| +--(6, 60)
| +--(7, 70)
+--(9, 90)
6 sor kijelölve.
*/