Давайте возьмем в качестве примера этих две таблиц: клиентов, содержащих клиент и продукта, содержащих продукты купили/используются клиентами.Oracle: ON DELETE CASCADE вызывает триггер рекурсию
Каждый товар ссылается на клиента через внешний ключ CustomerID, который соответствует первичному ключу таблицы Customer (они также имеют то же имя).
Когда клиент удаляется все продукты, которые ссылаются, что костюмера удаляются: Product.CustomerID имеет атрибут ON DELETE CASCADE.
Теперь давайте предположим, что клиент на базе должен, по крайней мере, иметь продукт:
, когда продукт будет удален, если он является последним продуктом костюмчика то костюмер должен быть удален.
CREATE OR REPLACE TRIGGER RemoveCustomer
AFTER DELETE ON Product
BEGIN
DELETE FROM Customer
WHERE CustomerID IN (
SELECT c.CustomerID
FROM Customer c
LEFT OUTER JOIN Product p
ON p.CustomerID = c.CustomerID
GROUP BY c.CustomerID HAVING COUNT(p.CustomerID) = 0
);
END;
/
Это решение кажется мне естественным, но Oracle ему не нравится. На каждом УДАЛИТЬ продукт я получаю ошибку:
ORA-00036: maximum number of recursive SQL levels (50) exceeded
Это даже если удаление не будет вызывать программу, которая будет удален.
Удивительно, но этот синтаксис работает просто отлично:
CREATE OR REPLACE TRIGGER RemoveCustomer
AFTER DELETE ON Product
BEGIN
FOR my_row IN (
SELECT c.CustomerID
FROM Customer c
LEFT OUTER JOIN Product p
ON p.CustomerID = c.CustomerID
GROUP BY c.CustomerID HAVING COUNT(p.CustomerID) = 0
)
LOOP
DELETE FROM Customer WHERE CustomerID = my_row.CustomerID;
END LOOP;
END;
/
Может кто-нибудь объяснить, почему это происходит?
EDIT:
Здесь есть рабочий пример:
CREATE TABLE Customer (
CustomerID INTEGER PRIMARY KEY
);
CREATE TABLE Product (
ProductID INTEGER PRIMARY KEY,
CustomerID INTEGER,
CONSTRAINT fk_Customer FOREIGN KEY (CustomerID)
REFERENCES Customer
ON DELETE CASCADE
);
INSERT INTo Customer (CustomerID) VALUES (0);
INSERT INTo Customer (CustomerID) VALUES (1);
INSERT INTo Customer (CustomerID) VALUES (2);
INSERT INTo Customer (CustomerID) VALUES (3);
INSERT INTo Customer (CustomerID) VALUES (4);
INSERT INTo Customer (CustomerID) VALUES (5);
INSERT INTo Customer (CustomerID) VALUES (6);
INSERT INTO Product (ProductID, CustomerID) VALUES (0, 0);
INSERT INTO Product (ProductID, CustomerID) VALUES (1, 0);
INSERT INTO Product (ProductID, CustomerID) VALUES (2, 1);
INSERT INTO Product (ProductID, CustomerID) VALUES (3, 2);
INSERT INTO Product (ProductID, CustomerID) VALUES (4, 3);
INSERT INTO Product (ProductID, CustomerID) VALUES (5, 3);
INSERT INTO Product (ProductID, CustomerID) VALUES (6, 3);
INSERT INTO Product (ProductID, CustomerID) VALUES (7, 4);
INSERT INTO Product (ProductID, CustomerID) VALUES (8, 5);
INSERT INTO Product (ProductID, CustomerID) VALUES (9, 5);
INSERT INTO Product (ProductID, CustomerID) VALUES (10, 6);
CREATE OR REPLACE TRIGGER RemoveCustomer
AFTER DELETE ON Product
BEGIN
DELETE FROM Customer
WHERE CustomerID IN (
SELECT c.CustomerID
FROM Customer c
LEFT OUTER JOIN Product p
ON p.CustomerID = c.CustomerID
GROUP BY c.CustomerID HAVING COUNT(p.CustomerID) = 0
);
END;
/
/* This request will produce the error */
DELETE FROM Product WHERE CustomerID = 3;
Со второй версией триггера, даже если цикл выполнен, рекурсия отсутствует. Это нормально? – TTK
@TTK - цикл выполняется, но не находит совпадающих строк во второй раз, поэтому удаление внутри цикла * не * выполняется снова. –
@Alex Poole - Если я прав, допустим, что нужно удалить только одного клиента: при первом запуске цикла он делает DELETE на клиенте. DELETE должен снова запустить триггер из-за инструкции каскадного удаления. На данный момент у нас есть один уровень рекурсии, но на этот раз триггер не войдет внутрь цикла, поскольку единственный клиент уже удален. Я прав? – TTK