使用触发器模拟DELETE ON CASCADE行为

时间:2014-08-03 20:08:02

标签: sql oracle triggers constraints

想象下面描述的两个表格。 这些表(transactionnumber列)之间存在“关系”,但它们不能符合外键约束,因为它们不是唯一的。

CREATE TABLE COMMAND_DATA
(
  TRANSACTIONNUMBER NUMBER(19) NOT NULL,
  KEYNAME VARCHAR2(255) NOT NULL,
  DATA VARCHAR2(255)
  CONSTRAINT PK_COMMAND_DATA PRIMARY KEY (COMMANDTRANSACTIONNUMBER, KEYNAME)
);

CREATE TABLE COMMAND
(
  TRANSACTIONNUMBER NUMBER(19) NOT NULL,
  COMMANDTYPE NUMBER(10) NOT NULL,
  TRANSACTIONTIMESTAMP NUMBER(19) NOT NULL,
  SOURCEID VARCHAR2(255),
  CONSTRAINT PK_COMMAND PRIMARY KEY (TRANSACTIONNUMBER, COMMANDTYPE)
);

现在,假设在COMMAND表中有10行包含transactionnumber = 1,在COMMAND_DATA表中有3行也包含transactionnumber = 1。这些数据每次都由批处理填充一次。现在我的系统正在从COMMAND表中逐个处理和删除行。

我想要实现的是,在从COMMAND表中删除transactionnumber = 1的最后一行之后,将从表COMMAND_DATA中删除所有具有相同transactionnumber的行。

所以我创建了以下触发器:

CREATE TRIGGER CLEAN_COMMAND_DATA
AFTER DELETE ON COMMAND FOR EACH ROW 

DECLARE
   pragma autonomous_transaction;
   v_count number(10);
BEGIN
   select count(*) into v_count from command where TRANSACTIONNUMBER = :old.TRANSACTIONNUMBER;

   if v_count = 0 then
      delete from COMMAND_DATA where TRANSACTIONNUMBER = :old.TRANSACTIONNUMBER;
   end if;
END;

但它不起作用,因为select语句也在计算正被删除的行,因此从不会出现count(*)返回零的情况。

我该如何调整触发器?或者有更好的解决方案吗?这里不能使用DELETE ON CASCADE因为我不能使用FK ...

1 个答案:

答案 0 :(得分:1)

正如我假设您已发现,您无法从同一个表中选择定义行级触发器;它导致表变异异常。

您尝试使用自治事务编译指示来解决此问题,尽管它删除了例外,但实际上只是覆盖了您的方法中的错误。您需要将处理移动到语句级别触发器。在Oracle 11g及更高版本中,您可以执行以下操作:

CREATE OR REPLACE TRIGGER clean_command_data
  FOR DELETE ON command
  COMPOUND TRIGGER

  -- Table to hold identifiers of inserted/updated transactions
  g_transactionNumbers sys.odcinumberlist;

BEFORE STATEMENT 
IS
BEGIN
-- Reset the internal transactions table
 g_transactionNumbers := sys.odcinumberlist();
END BEFORE STATEMENT; 

AFTER EACH ROW
IS
BEGIN
  -- Store the inserted/updated transactions
  g_transactionNumbers.EXTEND;
  g_transactionNumbers(g_transactionNumbers.LAST) := :old.transactionNumber;
END AFTER EACH ROW;

AFTER STATEMENT
IS
  CURSOR csr_commands
  IS
    SELECT DISTINCT
           tno.column_value transactionNumber
    FROM TABLE(g_transactionNumbers) tno
         LEFT OUTER JOIN command cmd
           ON (cmd.transactionNumber = tno.column_value)
    WHERE cmd.transactionNumber IS NULL;
BEGIN
  -- Check if for any deleted transaction there exists no more commands
  FOR r_command IN csr_commands LOOP
    DELETE FROM command_data
    WHERE transactionNumber = r_command.transactionNumber;
  END LOOP;
END AFTER STATEMENT;

END;