尝试使用 select 插入表时发生变异表错误

问题描述 投票:0回答:2

在 PLSQL 中从 select 插入数据时,我遇到了变异表错误的问题。

当我像这样插入时:

insert into pozycje_dokumentow
(
        pozycja_dokumentu,
            id_dok,
            stawka_vat,
             miejsce_skladowania,
             jm_kod_jednostka_miary,
             ilosc_jm_sprzedazy,
             ilosc,
             indeks_czesci
)
values(
        1014,
        1882706,
        23,
        4709,
        'L15',
        388.33,
        386.713,
        26539
);

一切都很好,但是当我尝试这样做时:

insert into pozycje_dokumentow
(
        pozycja_dokumentu,
            id_dok,
            stawka_vat,
             miejsce_skladowania,
             jm_kod_jednostka_miary,
             ilosc_jm_sprzedazy,
             ilosc,
             indeks_czesci
)
Select
        1014,
        1882706,
        23,
        4709,
        'L15',
        388.33,
        386.713,
        26539
from dual

我在表 pozycje_dokumentow 上的 before_insert 触发器中收到错误: ORA-20298: ORA-04091: ORA-04091: 表名正在发生变化,触发器/函数可能看不到它

这两个查询有什么区别?

仅当从创建触发器的同一个表中执行插入选择时,触发器主体才会生成错误:

select nvl(max(lp),0) + 1
    into :new.lp
    from pozycje_dokumentow
   where id_dok = :new.id_dok
   group by id_dok;
sql oracle plsql
2个回答
2
投票

您收到此错误是因为第一个版本执行单行

INSERT
,而第二个版本执行多行
INSERT
(因为您使用
SELECT
来生成值 - 从 Oracle 的角度来看)视图,您的
SELECT
返回多少行完全无关。唯一重要的是 Oracle 无法保证您的
SELECT
仅返回一行)。

我猜你有一个

BEFORE INSERT
行级触发器 - 这是多行
INSERT
和单行
INSERT
之间存在差异的唯一情况,请参阅关于变异表错误的数据库期刊文章 了解详情。

正如 @Littlefoot 在他们的回答中指出的那样 - 您需要重写触发器/切换到复合触发器,或者(更好)将应用程序逻辑完全移出触发器。


2
投票

我不会说是

insert
造成了差异。如果您运行第一个
insert
(“工作正常”)两次,我相信您会得到相同的错误。

这可能是因为触发代码从

pozycje_dokumentow
中选择,受
insert
影响的同一个表,所以它是mutating

我想你必须重写触发器(或者改变你做整个事情的方式)。


顺序方法有望解决您的问题。

create sequence seqa;

create or replace trigger trg_bi_podok 
  before insert on pozycje_dokumentow
  for each row
begin
  :new.id := seqa.nextval;
end;

至于“每个文档的唯一 PK”:有一种方法可以做到这一点。这是您可能使用的示例代码(即根据您的情况进行调整) - 它需要一个自治事务函数,该函数锁定包含 PK 值的表,获取下一个 PK 编号并释放该表。

CREATE TABLE EVIDENCIJA_BROJ
(
  DP        NUMBER(4)                           NOT NULL,
  REDNI_BR  NUMBER                              NOT NULL,
  WHAT      VARCHAR2(10 BYTE),
  GODINA    NUMBER(4)
);

   FUNCTION f_get_evidencija_broj (par_dp       IN NUMBER,
                                   par_what     IN VARCHAR2 DEFAULT 'EVID',
                                   par_godina   IN NUMBER DEFAULT NULL)
      RETURN NUMBER
   IS
      PRAGMA AUTONOMOUS_TRANSACTION;
      l_redni_br   evidencija_broj.redni_br%TYPE;
   BEGIN
          SELECT b.redni_br + 1
            INTO l_redni_br
            FROM evidencija_broj b
           WHERE     b.dp = par_dp
                 AND (   b.godina = par_godina
                      OR par_godina IS NULL)
                 AND b.what = par_what
      FOR UPDATE OF b.redni_br;

      UPDATE evidencija_broj b
         SET b.redni_br = l_redni_br
       WHERE     b.dp = par_dp
             AND b.what = par_what
             AND (   b.godina = par_godina
                  OR par_godina IS NULL);

      COMMIT;
      RETURN (l_redni_br);
   EXCEPTION
      WHEN NO_DATA_FOUND
      THEN
         LOCK TABLE evidencija_broj IN EXCLUSIVE MODE;

         INSERT INTO evidencija_broj (dp,
                                      godina,
                                      what,
                                      redni_br)
              VALUES (par_dp,
                      par_godina,
                      par_what,
                      1);

         COMMIT;
         RETURN (1);
   END f_get_evidencija_broj;
© www.soinside.com 2019 - 2024. All rights reserved.