在 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;
您收到此错误是因为第一个版本执行单行
INSERT
,而第二个版本执行多行 INSERT
(因为您使用 SELECT
来生成值 - 从 Oracle 的角度来看)视图,您的 SELECT
返回多少行完全无关。唯一重要的是 Oracle 无法保证您的 SELECT
仅返回一行)。
我猜你有一个
BEFORE INSERT
行级触发器 - 这是多行 INSERT
和单行 INSERT
之间存在差异的唯一情况,请参阅关于变异表错误的数据库期刊文章 了解详情。
正如 @Littlefoot 在他们的回答中指出的那样 - 您需要重写触发器/切换到复合触发器,或者(更好)将应用程序逻辑完全移出触发器。
我不会说是
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;