【问题标题】:Using Merge statement into a trigger在触发器中使用 Merge 语句
【发布时间】:2014-05-21 05:41:46
【问题描述】:

这是我要用于触发器的表:

CREATE TABLE "grace_period" (
    "id"    NUMBER(11)  PRIMARY KEY NOT NULL,
    "id_user"   NUMBER(20)  NOT NULL,
    "date_limit"    DATE    NOT NULL,
    "active"    NUMBER(11),
    "created_at"    DATE    NOT NULL,
    "updated_at"    DATE    
);

我想做的是在插入之前创建一个触发器,检查新条目是否已经包含"id_user"

如果"id_user" 存在,则为"id_user" 更新"active" 列,如果不存在,则应插入新行。


我设法将合并创建为触发器(在我将合并语句集成到 php 代码之前,将使用此触发器)但出现以下错误:

CREATE OR REPLACE TRIGGER "user_grace_changes"
BEFORE INSERT ON "grace_period"
FOR EACH ROW
BEGIN

MERGE INTO "grace_period" t1
  USING dual
     ON (t1."id_user" = :new."id_user") 
   WHEN MATCHED THEN
     UPDATE SET t1."active" = :new."active"
   WHEN NOT MATCHED THEN 
     INSERT( t1."id_user", t1."date_limit", t1."active" )
       VALUES( :new."id_user", :new."date_limit", :new."active" );

END;


insert into "grace_period" ("id_user","date_limit","active")
  VALUES (333, sysdate, 1);

> Informe de error - Error SQL: ORA-04091: la tabla
> PLATAFORMA.grace_period está mutando, puede que el disparador/la
> función no puedan verla ORA-06512: en "PLATAFORMA.user_grace_changes",
> línea 3 ORA-04088: error durante la ejecución del disparador
> 'PLATAFORMA.user_grace_changes' ORA-06512: en
> "PLATAFORMA.user_grace_changes", línea 3 ORA-04088: error durante la
> ejecución del disparador 'PLATAFORMA.user_grace_changes'
> 04091. 00000 -  "table %s.%s is mutating, trigger/function may not see it"
> *Cause:    A trigger (or a user defined plsql function that is referenced in
>            this statement) attempted to look at (or modify) a table that was
>            in the middle of being modified by the statement which fired it.
> *Action:   Rewrite the trigger (or function) so it does not read that table.

【问题讨论】:

  • 为什么不直接使用merge 语句而不是insert?为什么要使用触发器?
  • 嗨,Alex ...我不知道我为什么要尝试使用触发器 :( 那么合并语句会如何?。谢谢
  • 看看at the Oracle docs;看看你能不能把一些东西放在一起,如果你不能让它发挥作用,把你的尝试添加到问题中(也许可以改变标题)。
  • 这是得到的: MERGE INTO "grace_period" ("id_user","date_limit","active") t1 USING (SELECT 456 as "id_user", '2014-04-09 12: 00:00' 作为 "fecha",1 作为 "active" FROM dual) t2 ON (t1."id_user" = t2."id_user") WHEN MATCHED THEN UPDATE SET t1."active" = t2."active", WHEN不匹配则插入(t1.“id_user”,t1.“date_limit”,t1.“active”)值(t2.“id_user”,t2.“fecha”,t2.“active”)问候
  • 请将其作为格式化代码添加到问题中,而不是作为评论。这是否有效并做你想做的事? (实际上,如果是,则可以将其添加为答案;尽管最初的问题是关于触发器的,但我不太确定 *8-) 不相关的注释-您为什么使用带引号的标识符?如果您不这样做,生活会更轻松(代码也更容易阅读)。

标签: sql oracle triggers merge


【解决方案1】:

您不能在触发器中执行此操作。行级触发器通常不能查看或修改触发器所在的表中的数据,这就是当您尝试在其中执行merge 时得到 ORA-04091 的原因。如果您也尝试查询该表,则会出现错误。对于某些情况有一些解决方法,但我认为它们不适用于这种情况,并且即使它们这样做也会使您的架构过于复杂。

您不需要为此触发。您应该使用merge 而不是 insert。这是一个更复杂的语句,需要更多的输入,但是一旦它在应用程序中就无关紧要了。

根据您的表结构,而不是这样:

insert into "grace_period" ("id_user","date_limit","active")
  VALUES (333, sysdate, 1);

你可以这样做:

MERGE INTO "grace_period" target
USING (
  SELECT 333 AS "id_user", sysdate AS "date_limit", 1 AS "active" FROM dual
) source
ON (target."id_user" = source."id_user") 
WHEN MATCHED THEN
  UPDATE SET target."active" = source."active", "updated_at" = sysdate
WHEN NOT MATCHED THEN 
  INSERT("id", "id_user", "date_limit", "active", "created_at")
  VALUES("grace_seq".NEXTVAL, source."id_user", source."date_limit",
    source."active", sysdate);

您传递给insert 的值现在是using 子句中的伪列,从虚拟表dual 中选择。然后将这些与真实表中的现有记录进行比较。如果找到匹配项,则更新;否则插入。

我猜你想自动设置created_atupdated_at,以及主键id。您可能已经有一个触发器来从序列中设置它,但这里我是从序列中手动设置它,所以如果它与您已有的冲突,您可能需要从 insert 部分中删除它。

所以如果我使用显示的值运行它,333,sysdate,1,那么你会得到:

1 rows merged.

select * from "grace_period";

        id    id_user date_limit     active created_at updated_at
---------- ---------- ---------- ---------- ---------- ----------
         1        333 09-APR-14           1 09-APR-14             

如果我再次运行它,但将 active 设置为 0,那么你会得到:

1 rows merged.

select * from "grace_period";

        id    id_user date_limit     active created_at updated_at
---------- ---------- ---------- ---------- ---------- ----------
         1        333 09-APR-14           0 09-APR-14  09-APR-14  

如果你想让调用更容易,你可以将它包装在一个过程中:

create procedure merge_grace (p_id_user "grace_period"."id_user"%type,
  p_date_limit "grace_period"."date_limit"%type,
  p_active "grace_period"."active"%type) as
begin
  merge into "grace_period" target
  using (
    select p_id_user as "id_user", p_date_limit as "date_limit",
      p_active as "active"
    from dual
  ) source
  on (target."id_user" = source."id_user") 
  when matched then
    update set target."active" = source."active", "updated_at" = sysdate
  when not matched then 
    insert("id", "id_user", "date_limit", "active", "created_at")
    values("grace_seq".nextval, source."id_user", source."date_limit",
      source."active", sysdate);
end;
/

然后称呼它更友好:

exec merge_grace(333, sysdate, 1);

anonymous block completed

select * from "grace_period";

        id    id_user date_limit     active created_at updated_at
---------- ---------- ---------- ---------- ---------- ----------
         3        333 09-APR-14           1 09-APR-14             

exec merge_grace(333, sysdate, 0);

anonymous block completed

select * from "grace_period";

        id    id_user date_limit     active created_at updated_at
---------- ---------- ---------- ---------- ---------- ----------
         3        333 09-APR-14           0 09-APR-14  09-APR-14  

我在评论中提到了这一点,但我真的会认真重新考虑using quoted identifiers,因为它们使代码更难读写。如该文档所述,Oracle 不建议对数据库对象名称使用带引号的标识符。似乎没有任何明显的理由将所有内容都强制小写。当然,如果您正在创建一个新架构并且不需要担心很多现有对象,那会更容易......

【讨论】:

    【解决方案2】:

    使用下面的代码..

    CREATE TABLE "grace_period" (
        "id"    NUMBER(11)  PRIMARY KEY NOT NULL,
        "id_user"   NUMBER(20)  NOT NULL,
    
        "active"    NUMBER(11)
    
    );
    

    插入grace_period 值(1,123,1)

    create or replace PROCEDURE insert_on_grace(
            p_id      NUMBER,
            p_id_user NUMBER,
            p_active  NUMBER)
        AS
        BEGIN
          INSERT INTO grace_period VALUES
            (p_id,p_id_user,p_active);
        EXCEPTION
        WHEN dup_val_on_index THEN
          UPDATE grace_period
          SET "active" =1
          WHERE "id"   =p_id ;
        WHEN OTHERS THEN 
          RAISE_APPLICATION_ERROR(-20001,'Erros');
        END;
    

    效果很好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多