带有嵌套 BEFORE INSERT/UPDATE 触发器的原子事务

Atomic Transaction with nested BEFORE INSERT/UPDATE Triggers

目前我正在实施一个程序,该程序在模板中的一些相关 table 中创建几行。所以我的过程由一个 SAVEPOINT 后跟一些 INSERT 不同 table 的语句和一个游标组成,用于在引用新的 table 时向其他 table 插入更多行创建主键。

每个 table 都定义了一个 BEFORE INSERT/UPDATE 触发器,其目的是:

交易失败,显示 ORA-04091:table 正在发生变异,trigger/function 可能看不到它

我明白,我可以通过在每个触发器中声明 PRAGMA AUTONOMOUS TRANSACTION 来解决这个问题,但是我的事务将不再是原子的,因为所有这些数据集都应该是 created/inserted 作为一个整体或 None 个。

那么我在设计数据库时做错了什么?


更新:这是触发器的代码

CREATE TRIGGER TRG_AUFTRAG_B_IU
  BEFORE INSERT OR UPDATE
    ON AUFTRAG
  FOR EACH ROW
    BEGIN
    IF INSERTING THEN
    IF :new.id is NULL or :new.id = 0 THEN
      SELECT SEQ_AUFTRAG.nextval into :new.id from dual;
    END IF;

    IF :new.nummer is NULL or :new.nummer = 0 THEN
      SELECT nvl(MAX(NUMMER),0)+1 INTO :new.nummer FROM AUFTRAG WHERE EXTRACT(YEAR from DATUM) = EXTRACT(YEAR from :new.DATUM);
    END IF;

    --DEFAULT Values
    IF :new.BETR_GRENZWERTE_RELEVANT is NULL THEN
      SELECT 0 INTO :new.BETR_GRENZWERTE_RELEVANT FROM dual;
    END IF;

    IF :new.DOKUMENTE_ABGELEGT is NULL THEN
      SELECT 0 INTO :new.DOKUMENTE_ABGELEGT FROM dual;
    END IF;

    IF :new.EXT_ORG is NULL or :new.EXT_ORG < 1 THEN
      SELECT 1 INTO :new.EXT_ORG FROM dual;
    END IF;

    :new.ERSTELLT_VON := nvl(:new.ERSTELLT_VON,user);
    :new.ERSTELLT_DATUM := nvl(:new.ERSTELLT_DATUM,sysdate);
    END IF;

    :new.GEAENDERT_VON := user;
    :new.GEAENDERT_DATUM := sysdate;
    END;

你可以这样写得更紧凑:

CREATE TRIGGER TRG_AUFTRAG_B_IU
  BEFORE INSERT OR UPDATE
    ON AUFTRAG
  FOR EACH ROW
    BEGIN
    IF INSERTING THEN
        :new.id = NVL(NULLIF(:new.id, 0), SEQ_AUFTRAG.nextval);

        --DEFAULT Values
        :new.BETR_GRENZWERTE_RELEVANT := NVL(:new.BETR_GRENZWERTE_RELEVANT, 0);
        :new.DOKUMENTE_ABGELEGT := NVL(:new.DOKUMENTE_ABGELEGT, 0);

        IF :new.EXT_ORG is NULL or :new.EXT_ORG < 1 THEN
          :new.EXT_ORG := 1;
        END IF;    
        :new.ERSTELLT_VON := nvl(:new.ERSTELLT_VON,user);
        :new.ERSTELLT_DATUM := nvl(:new.ERSTELLT_DATUM,sysdate);
    END IF;

    :new.GEAENDERT_VON := user;
    :new.GEAENDERT_DATUM := sysdate;
END;

这部分只有"problem"

IF :new.nummer is NULL or :new.nummer = 0 THEN
    SELECT nvl(MAX(NUMMER),0)+1 INTO :new.nummer 
    FROM AUFTRAG 
    WHERE EXTRACT(YEAR from DATUM) = EXTRACT(YEAR from :new.DATUM);
END IF;

这个你应该像这样放入你的过程或语句触发器中(即没有 FOR EACH ROW 子句):

CREATE TRIGGER TRG_AUFTRAG_B_A
  AFTER INSERT ON AUFTRAG
BEGIN
    UPDATE 
        (SELECT ID, NUMMER,
             ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR from DATUM) ORDER BY ID) as N
        FROM AUFTRAG)
    SET NUMMER = N
    WHERE NUMMER IS NULL;        
END;