将Merge语句用于触发器

时间:2014-04-08 13:27:19

标签: sql oracle triggers merge

这是我将用于触发器的表格:

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"存在,则对"active""id_user"进行更新,如果不存在,则应插入新行。


我设法将合并创建为触发器(此触发器将被使用,直到我将merge语句集成到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.

2 个答案:

答案 0 :(得分: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不建议对数据库对象名称使用带引号的标识符。似乎没有任何明显的理由让一切都被迫小写。当然,如果您正在创建新架构并且不需要担心很多现有对象,这会更容易......

答案 1 :(得分:0)

使用以下代码..

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;

它工作得很好。