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

时间:2017-08-17 09:57:00

标签: database oracle transactions database-trigger

目前我正在实现一个过程,该过程在模板中的某些相关表中创建了几行。所以我的程序包括一个SAVEPOINT,后面是不同表上的一些INSERT语句,还有一个Cursor,用于在引用新创建的主键时向其他表插入更多行。

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

  • 如果未在INSERT语句中定义,则从序列发生器获取新的主键(在某些情况下,我需要明确地设置主键以便稍后在同一事务中引用它)
  • 如果为NULL,则设置一些默认值
  • 设置审核字段(last_change_date,last_change_user等)

交易失败, ORA-04091:表正在变异,触发器/功能可能看不到它

我理解,通过在每个触发器中声明PRAGMA AUTONOMOUS TRANSACTION,我可以解决这个问题,但我的事务不再是原子的,因为要求所有这些数据集都应该作为一个整体创建/插入或者没有。

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

更新:这是触发器的代码

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;

1 个答案:

答案 0 :(得分:1)

你可以写得更紧凑:

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;

这个部分只有“问题”

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;