Narazili jste na klasickou výjimku „mutující tabulky“. Ve spouštěči ROW vám Oracle neumožňuje spouštět dotaz proti tabulce, na které je spouštěč definován – je to tedy SELECT
proti TABLE1 v DELETING
část spouštěče, která tento problém způsobuje.
Existuje několik způsobů, jak to obejít. V této situaci je možná nejlepší použít složený trigger, který by vypadal asi takto:
CREATE OR REPLACE TRIGGER TABLE1_NUM_TRG
FOR INSERT OR DELETE ON TABLE1
COMPOUND TRIGGER
TYPE NUMBER_TABLE IS TABLE OF NUMBER;
tblTABLE2_IDS NUMBER_TABLE;
BEFORE STATEMENT IS
BEGIN
tblTABLE2_IDS := NUMBER_TABLE();
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
IF INSERTING THEN
UPDATE TABLE2 t2
SET t2.TABLE2NUM = :new.NUM
WHERE t2.ID = :new.TABLE2_ID;
ELSIF DELETING THEN
tblTABLE2_IDS.EXTEND;
tblTABLE2_IDS(tblTABLE2_IDS.LAST) := :new.TABLE2_ID;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
IF tblTABLE2_IDS.COUNT > 0 THEN
FOR i IN tblTABLE2_IDS.FIRST..tblTABLE2_IDS.LAST LOOP
UPDATE TABLE2 t2
SET t2.TABLE2NUM = (SELECT NUM
FROM (SELECT t1.NUM
FROM TABLE1 t1
WHERE t1.TABLE2_ID = tblTABLE2_IDS(i)
ORDER BY modification_date DESC)
WHERE ROWNUM = 1)
WHERE t2.ID = tblTABLE2_IDS(i);
END LOOP;
END IF;
END AFTER STATEMENT;
END TABLE1_NUM_TRG;
Složený spouštěč umožňuje každý časový bod (BEFORE STATEMENT
, BEFORE ROW
, AFTER ROW
a AFTER STATEMENT
), které mají být řešeny. Všimněte si, že časové body jsou vždy vyvolány v daném pořadí. Když je příslušný příkaz SQL (tj. INSERT INTO TABLE1
nebo DELETE FROM TABLE1
) se provede a spustí se tento spouštěč, první časový bod, který bude vyvolán, bude BEFORE STATEMENT
a kód v BEFORE STATEMENT
handler alokuje PL/SQL tabulku pro uložení hromady čísel. V tomto případě čísla, která mají být uložena v tabulce PL/SQL, budou hodnoty TABLE2_ID z TABLE1. (Tabulka PL/SQL se používá místo například pole, protože tabulka může obsahovat různý počet hodnot, zatímco pokud bychom použili pole, museli bychom předem vědět, kolik čísel bychom museli uložit. Nemůžeme předem vědět, kolik řádků bude ovlivněno konkrétním příkazem, takže použijeme tabulku PL/SQL).
Když se zobrazí AFTER EACH ROW
je dosaženo časového bodu a zjistíme, že zpracovávaný příkaz je INSERT, spouštěč prostě pokračuje a provede nezbytnou AKTUALIZACI TABULKY2, protože to nezpůsobí problém. Pokud se však provádí DELETE, spouštěč uloží TABLE1.TABLE2_ID do dříve přidělené tabulky PL/SQL. Když se zobrazí AFTER STATEMENT
je konečně dosaženo časového bodu, je iterována dříve přidělená tabulka PL/SQL a pro každé nalezené TABLE2_ID je provedena příslušná aktualizace.
Dokumentace zde.