2016-11-01 22 views
4

Давайте возьмем в качестве примера этих две таблиц: клиентов, содержащих клиент и продукта, содержащих продукты купили/используются клиентами.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; 

ответ

2

Удивительно, но оказывается, что каскадное удаление заявление productsвсегда происходит после удаления выполняется на customers - даже если клиенты не удалены. Например:

SQL> delete customer where customerid = 9999999; 
delete customer where customerid = 9999999 
     * 
ERROR at line 1: 
ORA-00036: maximum number of recursive SQL levels (50) exceeded 
ORA-06512: at "TTEST.REMOVECUSTOMER", line 2 
... 

С вашей второй версией триггера, тело for цикла никогда не выполняется, когда нет клиентов без каких-либо продуктов, поэтому удаления customers никогда не происходит, и бесконечный цикл избежать.

+0

Со второй версией триггера, даже если цикл выполнен, рекурсия отсутствует. Это нормально? – TTK

+1

@TTK - цикл выполняется, но не находит совпадающих строк во второй раз, поэтому удаление внутри цикла * не * выполняется снова. –

+0

@Alex Poole - Если я прав, допустим, что нужно удалить только одного клиента: при первом запуске цикла он делает DELETE на клиенте. DELETE должен снова запустить триггер из-за инструкции каскадного удаления. На данный момент у нас есть один уровень рекурсии, но на этот раз триггер не войдет внутрь цикла, поскольку единственный клиент уже удален. Я прав? – TTK

 Смежные вопросы

  • Нет связанных вопросов^_^