【问题标题】:Oracle upsert trigger error "ORA-00036: maximum number of recursive SQL levels (50) exceeded"Oracle upsert 触发器错误“ORA-00036: 超出最大递归 SQL 级别数 (50)”
【发布时间】:2018-08-16 20:31:42
【问题描述】:

我一直在研究一个简单的 upsert 触发器,它使用与一个名为 vibki 的列相关的唯一索引。

表 DLL 信息以便于访问:

  CREATE TABLE "D2C_EVENT_GENERATION_BOM" 
   (    "VIBKI" VARCHAR2(40 BYTE), 
    "STATUS" VARCHAR2(100 BYTE), 
    "LASTRUNTIME" DATE
   );
CREATE UNIQUE INDEX "D2C_EVENT_GENERATION_BOM_IND" ON ."D2C_EVENT_GENERATION_BOM" ("VIBKI");
--------------------------------------------------------
--  Constraints for Table D2C_EVENT_GENERATION_BOM
--------------------------------------------------------

ALTER TABLE "D2C_EVENT_GENERATION_BOM" MODIFY ("VIBKI" NOT NULL ENABLE);

这是我的触发器 PL/SQL:

create or replace TRIGGER "BL_D2C_EVENT_GENERATION_BOM" 
BEFORE INSERT
   ON D2C_EVENT_GENERATION_BOM REFERENCING NEW AS NEW OLD AS OLD 
FOR EACH ROW
BEGIN
    UPDATE D2C_EVENT_GENERATION_BOM SET vibki = :NEW.vibki, status = 'NEW', lastruntime = sysdate
            WHERE vibki = :NEW.vibki;
        IF ( sql%notfound ) THEN
            INSERT INTO D2C_EVENT_GENERATION_BOM (vibki,status,lastruntime)
            VALUES (:NEW.vibki,'NEW',sysdate);
    END IF;
END;

看起来很简单,我只是首先尝试更新,如果没有找到,请插入。

但是这个触发器不适用于这个插入语句:

INSERT INTO D2C_EVENT_GENERATION_BOM (vibki) VALUES ('TAS2002/01');

例外:

Error starting at line : 1 in command -
INSERT INTO D2C_EVENT_GENERATION_BOM (vibki) VALUES ('TAS2002/01')
Error report -
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-00036: maximum number of recursive SQL levels (50) exceeded

请有人支持我如何实现这个错误,我在另一个表上使用相同的触发器,唯一的区别是我在 upsert 操作之前进行选择操作以收集一些值,所以我希望这段代码能够轻松工作,但是它不工作。

任何帮助将不胜感激,非常感谢!

【问题讨论】:

    标签: oracle plsql triggers upsert


    【解决方案1】:

    首先是sql%rowcount,而不是sql%notfound。对于更新 sql%notfound 始终为 false。
    其次,您的触发器是递归的。
    触发器在 D2C_EVENT_GENERATION_BOM 表上
    1. 如果 sql%rowcount =0 则插入 D2C_EVENT_GENERATION_BOM
    2. 为插入执行触发器(从第 1 点开始),仍然有 sql%rowcount = 0 然后插入 ...

    这个模式重复了 50 次。

    要修复它,您必须通知下一个触发器以中断递归。 最简单的方法是添加前缀并检查它。

    create or replace TRIGGER "BL_D2C_EVENT_GENERATION_BOM" 
    
        BEFORE INSERT
           ON D2C_EVENT_GENERATION_BOM REFERENCING NEW AS NEW OLD AS OLD 
        FOR EACH ROW
        BEGIN
          if :NEW.vibki not like '-%' then  
            UPDATE D2C_EVENT_GENERATION_BOM SET vibki = :NEW.vibki, status = 'NEW', lastruntime = sysdate
                    WHERE vibki = :NEW.vibki;            
                IF ( sql%rowcount = 0 ) THEN
                    INSERT INTO D2C_EVENT_GENERATION_BOM (vibki,status,lastruntime)
                    VALUES ('-'||:NEW.vibki,'NEW',sysdate);
            END IF;
          else 
           :NEW.vibki := substr(:NEW.vibki,2);
          end if; 
        END;
    

    解决方案 2) 带视图和触发器

    create view V_D2C_EVENT_GENERATION_BOM as (select * from  D2C_EVENT_GENERATION_BOM);
    
    CREATE OR REPLACE TRIGGER T_D2C_EVENT_GENERATION_BOM
    INSTEAD OF INSERT
    ON V_D2C_EVENT_GENERATION_BOM
    FOR EACH ROW
    begin 
       UPDATE D2C_EVENT_GENERATION_BOM SET vibki = :NEW.vibki, status = 'NEW', lastruntime = sysdate
                WHERE vibki = :NEW.vibki;
            IF ( sql%rowcount = 0 ) THEN
                INSERT INTO D2C_EVENT_GENERATION_BOM (vibki,status,lastruntime)
                VALUES (:NEW.vibki,'NEW',sysdate);
        END IF;  
    end;
    
    INSERT INTO V_D2C_EVENT_GENERATION_BOM (vibki) VALUES ('11TAS2002/01');
    

    解决方案 3) 需要合并 0 个触发器

    merge into D2C_EVENT_GENERATION_BOM a
     using (select 'your_id' vibki  from dual) b on (a.vibki =b.vibki)
    WHEN MATCHED THEN 
     UPDATE set status = 'NEW', lastruntime = sysdate
                WHERE a.vibki = b.vibki
    WHEN not MATCHED THEN 
     insert (vibki) values( b.vibki)         ;
    

    【讨论】:

    • 这正在创建唯一约束 viaolation:/ INSERT INTO D2C_EVENT_GENERATION_BOM (vibki) VALUES ('TAS2002/01') 错误报告 - ORA-00001:违反唯一约束 (PSBD.D2C_EVENT_GENERATION_BOM_IND)
    • 好吧,我大概知道你想达到什么目的
    • 解决方案 3:不要使用触发器,而是使用 Merge 语句来获得我的投票!
    • 您能否描述一下为什么在解决方案 2 中需要特殊视图?
    • 对于解决方案 1) 我不明白 if :NEW.vibki not like '-%' then 的原因。另外我假设这个触发器会失败,ORA-04091: table D2C_EVENT_GENERATION_BOM is mutating... 解决方案 2) 和解决方案 3) 要好得多。
    【解决方案2】:

    您的触发器有几个问题:

    • 对于BEFORE INSERT 触发器,您不能有任何OLD 值。

    • UPDATE [...] SET vibki = :NEW.vibki [...] WHERE vibki = :NEW.vibki好像没用

    • IF sql%notfound THEN 在这种情况下永远不会是 TRUE。

    我不知道你想达到什么目的,但可能很简单

    create or replace TRIGGER BL_D2C_EVENT_GENERATION_BOM
       BEFORE INSERT ON D2C_EVENT_GENERATION_BOM 
       FOR EACH ROW
    BEGIN
        :NEW.status := 'NEW';
        :NEW.lastruntime := sysdate;
    END;
    

    REFERENCING NEW AS NEW OLD AS OLD是默认的,可以跳过。

    【讨论】:

    • 这没有被编译,因为 :NEW 不能被编辑...感谢您的好心 cmets 但“IF sql%notfound THEN”是有效的语法。请查看docs.oracle.com/cd/B10501_01/appdev.920/a96624/13_elems48.htm
    • 不用担心。但它并不像预期的那样对我有用。如果该行不存在,则期望将该行插入表中。如果行已经存在,只需将 update set status = 'NEW' 和 lastupdatetime 设置为系统日期。目前它插入但更新不如预期。
    • 当你说“如果行不存在时插入”是矛盾的。当你有一个插入触发器时,插入的行必须始终存在(否则它不会是一个插入)。如果插入因任何原因失败,例如因为您尝试为 VIBKI 插入 NULL,所以触发器不会触发。同样,对我来说,这个触发器的意图是什么还不清楚。请解释您的业务逻辑。
    • 当然,让我试着简单地再告诉你一遍。我们有一个批处理操作系统,在这个表上执行插入操作,关于这 3 列(VIBKI(PK),STATUS,LASTRUNTIME),我们将制作一个应用程序,它将用“NEW”值监听状态并进行一些操作,但是为了处理表中的新行更新,我们想使用简单的触发器,即使我们的批处理消费者创建了 INSERT 操作,我们将首先检查是否已经存在现有行,然后只更新 LASTRUNTIME 和 STATUS。如果没有,检查将显示我们应该插入而不是更新。
    • edit你的问题,而不是把额外的信息作为评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 2017-03-12
    • 1970-01-01
    • 2014-09-14
    相关资源
    最近更新 更多