Sql 在触发器中使用Merge语句

Sql 在触发器中使用Merge语句,sql,oracle,triggers,merge,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"

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

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\u user

如果id\u用户存在,则对该id\u用户的活动列进行更新,如果该id\u用户不存在,则应插入新行

我成功地创建了merge-into触发器。在将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.

你不能用扳机来做这件事。行级触发器通常不能查看或修改触发器所在表中的数据,这就是为什么在尝试在其中进行合并时会得到ORA-04091。如果您也尝试查询该表,则会出现错误。有些场景有变通方法,但我认为它们不适用于这种情况,即使有,也会使您的模式过于复杂

你不需要扳机。您应该使用合并而不是插入。这是一个更复杂的语句,需要更多的输入,但一旦它出现在应用程序中就不重要了

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

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_at和updated_at,以及主键id。您可能已经有了一个触发器,可以从序列中设置它,但这里我将从序列中手动设置它,因此如果它与您已有的冲突,您可能需要从插入零件中删除它

因此,如果我使用所示的值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             
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  
如果我再次运行它,但将active设置为0,则您会得到:

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             
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  
我在评论中提到了这一点,但我真的会认真地重新考虑,因为这会使代码更难读写。正如该文档所说,Oracle不建议对数据库对象名称使用带引号的标识符。似乎没有任何明显的理由将所有内容强制简化。当然,如果您正在创建一个新的模式,并且没有太多的现有对象需要担心,那么这会更容易…

使用下面的代码

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

    "active"    NUMBER(11)

);
插入宽限期 值1123,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;

它工作得很好。

为什么不使用merge语句而不是insert语句呢?你为什么要用扳机?嗨Alex。。。我不知道我为什么尝试使用触发器:merge语句会是什么样子?。谢谢你看一眼;看看你是否能把一些东西放在一起,如果你不能让它起作用,把你的尝试添加到问题中,并可能更改标题。这就是我们得到的:合并到宽限期id用户、日期限制、活动t1,使用SELECT 456作为id用户,“2014-04-09 12:00:00”作为fecha,1在t1.id_用户=t2.id_用户匹配时从双t2激活,然后更新集t1.active=t2.active,不匹配时插入t1.id_用户,t1.date_限制,t1.active值t2.id_用户,t2.fecha,t2.active Regard请将其作为格式化代码添加到问题中,而不是作为注释。你想做什么就做什么?事实上,如果是的话,也许可以加上它作为答案;虽然最初的问题是关于触发器的,但我不太确定*8-不相关的注释-为什么要使用带引号的标识符?当你不这样做的时候,生活更容易,代码也更容易阅读。