如何在不引发 ORA-04091 变异 table 错误的情况下更新 table 并在同一触发器中抛出错误
How to update a table AND throw an error in the same trigger without raising ORA-04091 mutating table error
我的作业要求我创建一个触发器,在某些情况下,它会更新一个值并引发错误。我正在使用通过终端上的 sqlplus 命令访问的 Oracle 数据库。
- 我不能使用 « :NEW.attributeName := value » 因为引发错误会阻止更新。
- 我不能在触发器中使用更新语句,因为它会引发变异 table 错误。
- 根据问题陈述,我应该能够在一个触发器内解决这个问题,问题陈述对此有点含糊,但我认为我也不允许使用过程。
- 我们在 class 中没有看到临时的 table,所以它们不能用于作业。然而,我们看到了意见。
CREATE OR REPLACE TRIGGER triggerName
INSTEAD OF UPDATE OF attributeName ON TableName
FOR EACH ROW
BEGIN
IF (:NEW.attributeName < 0) THEN
UPDATE TableName
SET attributeName = 0
WHERE attr0 = :NEW.attr0 AND
attr1 = :NEW.attr1
;
raise_application_error(-20101, 'You cannot update attributeName to a negative value.');
END;
/
我尝试使用以下示例中描述的视图 (https://sgbd.developpez.com/oracle/ora-04091/#LI)。它是法语的;它只是说创建视图应该允许您使用 INSTEAD OF 触发器,但我确实这样做了,现在当我更新 table.
时不再触发触发器
CREATE TABLE CLIENT(
IDC INTEGER PRIMARY KEY ,
NOM VARCHAR2 (40));
CREATE TABLE VOYAGE(
IDV INTEGER PRIMARY KEY ,
DESTINATION VARCHAR2 (40),
MAXPLACE INTEGER ) -- nombre total de places
;
CREATE TABLE INSCRIPTION(
IDC INTEGER REFERENCES CLIENT(IDC),
IDV INTEGER REFERENCES VOYAGE(IDV),
DATERESERV DATE ,
CONSTRAINT INSCRIPTION_PK PRIMARY KEY (IDC, IDV));
INSERT INTO CLIENT(IDC, NOM) VALUES (1, 'DURAND');
INSERT INTO CLIENT(IDC, NOM) VALUES (2, 'DUBOIS');
INSERT INTO CLIENT(IDC, NOM) VALUES (3, 'DUGENOU');
COMMIT ;
INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (10, 'VENISE', 25);
INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (11, 'PRAGUE', 20);
COMMIT ;
-- Création d'une vue sur la table INSCRIPTION pour le support des déclencheurs INSTEAD OF
CREATE OR REPLACE VIEW V_INSCRIPTION AS SELECT * FROM INSCRIPTION;
CREATE OR REPLACE TRIGGER TRIG_V_INSCRIPTION INSTEAD OF INSERT ON V_INSCRIPTION FOR EACH ROW
DECLARE
NB_RESERVE INTEGER ; -- nombre de réservations déjà faites
NB_MAXPLACE INTEGER ; -- nombre de places total
BEGIN
SELECT COUNT (*) INTO NB_RESERVE FROM V_INSCRIPTION
WHERE IDC=:NEW.IDC
AND IDV=:NEW.IDV;
SELECT MAXPLACE INTO NB_MAXPLACE FROM VOYAGE
WHERE IDV=:NEW.IDV;
IF NB_MAXPLACE - NB_RESERVE < 1 THEN
DBMS_OUTPUT.PUT_LINE('Désolé, voyage complet');
ELSE
-- dans un déclencheur INSTEAD OF, l'instruction DML sous-jacente ne s'exécute pas. On traite donc l'insertion manuellement
INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) VALUES (:NEW.IDC, :NEW.IDV, :NEW.DATERESERV);
END IF ;
END ;
/
-- DUGENOU aimerait bien aller à Venise :
INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 3, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
1 ligne créée.
有没有什么方法可以让我在不使用过程的情况下更新 attributeName 的值并引发错误 ORA-20101?否则,我会假设我被允许。
哇。这是一组要求中的一个。首先,如果您希望在引发异常时保留任何更改,您将需要创建一个单独的过程并使用 pragma autonomous_transaction 语句。
其次,如果您正在更新触发器触发的相同 table 并且您只能有一个触发器,那么避免突变 table 触发器的唯一方法是使用 复合触发器。这是一个 LiveSQL 脚本的 link,它将为您提供大量代码供您使用。 https://livesql.oracle.com/apex/livesql/file/content_CGRC9SJRBTH83GTAAWUB1H4JG.html
第三,这完全是个坏主意。 DML 触发器不应包含 DML 本身。潜在问题和副作用太多。
相反,创建一个包含所有必要逻辑的过程,并让开发人员在执行更新时调用该过程。
事实证明,老师的意思是我们使用 DBMS 输出来显示 "error message" 而不是实际引发错误。然后解决方案变成
CREATE OR REPLACE TRIGGER triggerName
BEFORE UPDATE OF attributeName ON TableName
FOR EACH ROW
BEGIN
IF (:NEW.attributeName < 0) THEN
:NEW.attributeName := 0;
DBMS_OUTPUT.PUT_LINE('You cannot update attributeName to a negative value.');
END;
/
我的作业要求我创建一个触发器,在某些情况下,它会更新一个值并引发错误。我正在使用通过终端上的 sqlplus 命令访问的 Oracle 数据库。
- 我不能使用 « :NEW.attributeName := value » 因为引发错误会阻止更新。
- 我不能在触发器中使用更新语句,因为它会引发变异 table 错误。
- 根据问题陈述,我应该能够在一个触发器内解决这个问题,问题陈述对此有点含糊,但我认为我也不允许使用过程。
- 我们在 class 中没有看到临时的 table,所以它们不能用于作业。然而,我们看到了意见。
CREATE OR REPLACE TRIGGER triggerName
INSTEAD OF UPDATE OF attributeName ON TableName
FOR EACH ROW
BEGIN
IF (:NEW.attributeName < 0) THEN
UPDATE TableName
SET attributeName = 0
WHERE attr0 = :NEW.attr0 AND
attr1 = :NEW.attr1
;
raise_application_error(-20101, 'You cannot update attributeName to a negative value.');
END;
/
我尝试使用以下示例中描述的视图 (https://sgbd.developpez.com/oracle/ora-04091/#LI)。它是法语的;它只是说创建视图应该允许您使用 INSTEAD OF 触发器,但我确实这样做了,现在当我更新 table.
时不再触发触发器CREATE TABLE CLIENT(
IDC INTEGER PRIMARY KEY ,
NOM VARCHAR2 (40));
CREATE TABLE VOYAGE(
IDV INTEGER PRIMARY KEY ,
DESTINATION VARCHAR2 (40),
MAXPLACE INTEGER ) -- nombre total de places
;
CREATE TABLE INSCRIPTION(
IDC INTEGER REFERENCES CLIENT(IDC),
IDV INTEGER REFERENCES VOYAGE(IDV),
DATERESERV DATE ,
CONSTRAINT INSCRIPTION_PK PRIMARY KEY (IDC, IDV));
INSERT INTO CLIENT(IDC, NOM) VALUES (1, 'DURAND');
INSERT INTO CLIENT(IDC, NOM) VALUES (2, 'DUBOIS');
INSERT INTO CLIENT(IDC, NOM) VALUES (3, 'DUGENOU');
COMMIT ;
INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (10, 'VENISE', 25);
INSERT INTO VOYAGE(IDV, DESTINATION, MAXPLACE) VALUES (11, 'PRAGUE', 20);
COMMIT ;
-- Création d'une vue sur la table INSCRIPTION pour le support des déclencheurs INSTEAD OF
CREATE OR REPLACE VIEW V_INSCRIPTION AS SELECT * FROM INSCRIPTION;
CREATE OR REPLACE TRIGGER TRIG_V_INSCRIPTION INSTEAD OF INSERT ON V_INSCRIPTION FOR EACH ROW
DECLARE
NB_RESERVE INTEGER ; -- nombre de réservations déjà faites
NB_MAXPLACE INTEGER ; -- nombre de places total
BEGIN
SELECT COUNT (*) INTO NB_RESERVE FROM V_INSCRIPTION
WHERE IDC=:NEW.IDC
AND IDV=:NEW.IDV;
SELECT MAXPLACE INTO NB_MAXPLACE FROM VOYAGE
WHERE IDV=:NEW.IDV;
IF NB_MAXPLACE - NB_RESERVE < 1 THEN
DBMS_OUTPUT.PUT_LINE('Désolé, voyage complet');
ELSE
-- dans un déclencheur INSTEAD OF, l'instruction DML sous-jacente ne s'exécute pas. On traite donc l'insertion manuellement
INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) VALUES (:NEW.IDC, :NEW.IDV, :NEW.DATERESERV);
END IF ;
END ;
/
-- DUGENOU aimerait bien aller à Venise :
INSERT INTO INSCRIPTION(IDC, IDV, DATERESERV) SELECT 3, 10, TO_DATE(SYSDATE, 'DD/MM/YYYY') FROM DUAL ;
1 ligne créée.
有没有什么方法可以让我在不使用过程的情况下更新 attributeName 的值并引发错误 ORA-20101?否则,我会假设我被允许。
哇。这是一组要求中的一个。首先,如果您希望在引发异常时保留任何更改,您将需要创建一个单独的过程并使用 pragma autonomous_transaction 语句。
其次,如果您正在更新触发器触发的相同 table 并且您只能有一个触发器,那么避免突变 table 触发器的唯一方法是使用 复合触发器。这是一个 LiveSQL 脚本的 link,它将为您提供大量代码供您使用。 https://livesql.oracle.com/apex/livesql/file/content_CGRC9SJRBTH83GTAAWUB1H4JG.html
第三,这完全是个坏主意。 DML 触发器不应包含 DML 本身。潜在问题和副作用太多。
相反,创建一个包含所有必要逻辑的过程,并让开发人员在执行更新时调用该过程。
事实证明,老师的意思是我们使用 DBMS 输出来显示 "error message" 而不是实际引发错误。然后解决方案变成
CREATE OR REPLACE TRIGGER triggerName
BEFORE UPDATE OF attributeName ON TableName
FOR EACH ROW
BEGIN
IF (:NEW.attributeName < 0) THEN
:NEW.attributeName := 0;
DBMS_OUTPUT.PUT_LINE('You cannot update attributeName to a negative value.');
END;
/