如何使用返回多个值的子查询编写正确的触发器

How to write a correct trigger with a subquery which is returning more than one value

我需要创建一个触发器,而不是在视图中插入一行,而是在 3 个表中插入值。 这是正确的触发器:

 INSTEAD OF INSERT ON Retete_vegetariane
 FOR EACH ROW
BEGIN
    INSERT INTO categorie(categ_id, tip)
    VALUES(:NEW.categ_id, :NEW.tip);

    INSERT INTO Ingredient(ingred_id, ingredient)
    VALUES(:NEW.ingred_id, :NEW.ingredient);

    INSERT INTO Reteta(reteta_id, nume, descriere, categ_id, vegetariana, timp_preparare, portii)
    VALUES(:NEW.reteta_id, :NEW.reteta, :NEW.descriere, :NEW.categ_id,'D', :NEW.timp_preparare, :NEW.portii);
    
    INSERT INTO Set_Ingrediente(reteta_id, ingred_id, cantitate, um, comentarii)
    VALUES (:NEW.reteta_id, :NEW.ingred_id, :NEW.cantitate, :NEW.um, :NEW.comentarii);
END;
/

现在我想更新触发器以在 reteta_id 已经存在时引发应用程序错误。它仍然引发错误说 reteta_id 是主键,但现在我想让它说它已经存在。 这是我试过的代码,但它说你不能在触发器中使用这样的子查询。

CREATE OR REPLACE TRIGGER ad_vegetarian
 INSTEAD OF INSERT ON Retete_vegetariane
 FOR EACH ROW
DECLARE 
 variabil reteta.reteta_id%type;
BEGIN
variabil:=:NEW.reteta_id;
 IF variabil EXISTS(
    SELECT DISTINCT reteta_id FROM reteta;) THEN
    RAISE_APPLICATION_ERROR(-20512,'Reteta_id  already exists');
END IF;
    INSERT INTO categorie(categ_id, tip)
    VALUES(:NEW.categ_id, :NEW.tip);
    INSERT INTO Ingredient(ingred_id, ingredient)
    VALUES(:NEW.ingred_id, :NEW.ingredient);
    INSERT INTO Reteta(reteta_id, nume, descriere, categ_id, vegetariana, timp_preparare, portii)
    VALUES(:NEW.reteta_id, :NEW.reteta, :NEW.descriere, :NEW.categ_id,'D', :NEW.timp_preparare, :NEW.portii);
    INSERT INTO Set_Ingrediente(reteta_id, ingred_id, cantitate, um, comentarii)
    VALUES (:NEW.reteta_id, :NEW.ingred_id, :NEW.cantitate, :NEW.um, :NEW.comentarii);
END;/

将子查询移出 IF:

...
begin
  select nvl(max(1), 0)
  into variabil
  from dual 
  where exists (select null
                from reteta
                where reteta_id = :new.reteta_id
               );

  if variabil = 1 then
     raise_application_error(-20512, 'Reteta_id already exists');
  end if;  
  ...             

如果您查看 IF, you'll find that it accepts boolean_expression after IF. boolean_expression 定义的文档,则在 conditional_predicate 分支中列出这些替代方案:

{ collection.EXISTS ( index )
| expression { IS [ NOT ] NULL
             | [ NOT ] { BETWEEN expression AND expression
                       | IN ( expression [, expression ]... )
                       | LIKE pattern
                       }
             | relational_operator expression
             }
| { named_cursor | SQL } % { FOUND | ISOPEN | NOTFOUND }
}

它不允许你放置 EXISTS 谓词,因为它是一个 SQL 谓词。

您可以避免双重检查(在 select 和后续插入时)是否存在:执行 insert 并处理 dup_val_on_index 异常。

create table t1 (
  id1 int primary key,
  val1 varchar2(100)
)
create table t2 (
  id2 int primary key,
  val2 varchar2(100),
  id1 references t1(id1)
)
create view v_test as
select *
from t1
  join t2
  using(id1)
create trigger trg_test
instead of insert on v_test
for each row
begin
  insert into t1(id1, val1)
  values (:new.id1, :new.val1);
  
  begin
    insert into t2(id2, val2, id1)
    values(:new.id2, :new.val2, :new.id1);
  exception
    when dup_val_on_index then
      RAISE_APPLICATION_ERROR(-20512,'Reteta_id  already exists');
  end;
end;
/
insert into v_test(id1, val1, id2, val2)
values(1, 'A', 1, 'B')
1 rows affected
insert into v_test(id1, val1, id2, val2)
values(2, 'AA', 1, 'B')
ORA-20512: Reteta_id  already exists
ORA-06512: at "FIDDLE_CPKPBNEZVLNFZLUOGPNZ.TRG_TEST", line 10
ORA-04088: error during execution of trigger 'FIDDLE_CPKPBNEZVLNFZLUOGPNZ.TRG_TEST'

db<>fiddle here