【问题标题】:How to update a table AND throw an error in the same trigger without raising ORA-04091 mutating table error如何更新表并在同一个触发器中引发错误而不引发 ORA-04091 变异表错误
【发布时间】:2019-09-16 21:21:11
【问题描述】:

我的作业要求我创建一个触发器,该触发器在某些情况下会更新值并引发错误。我正在使用通过终端上的 sqlplus 命令访问的 Oracle 数据库。

  • 我不能使用 « :NEW.attributeName := value » 因为引发错误会阻止更新。
  • 我不能在触发器中使用更新语句,因为它会引发变异表错误。
  • 根据问题陈述,我应该能够在一个触发器内解决这个问题,并且问题陈述对此有点模糊,但我认为我也不允许使用过程。
  • 我们在课堂上没有看到临时表,因此它们不能用于家庭作业。但是,我们看到了视图。
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 触发器,但我正是这样做的,现在当我更新表时不再触发触发器。

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?否则,我会假设我被允许这样做。

【问题讨论】:

  • 您对问题的描述不明确。您显示的第一个触发器是instead of update 触发器,第二个触发器是instead of insert。而且,他们似乎在做完全不同的事情。你具体需要做什么?更新还是插入?什么时候应该提出错误?插入后还是更新后?
  • 第二个只是我从中获得灵感的一个例子。我实际上必须为更新而不是插入编写触发器。无论如何,事实证明,当老师说我们需要包含错误消息时,他们是想让我们使用 DBMS 输出。

标签: oracle plsql oracle11g database-trigger


【解决方案1】:

哇。这是一组要求。首先,如果您希望在引发异常的情况下保留任何更改,您将需要创建一个单独的过程并使用 pragma automatic_transaction 语句。

其次,如果您要更新触发器触发的同一个表并且您只能有一个触发器,那么避免变异表触发器的唯一方法是使用 compound 触发器。这是一个 LiveSQL 脚本的链接,它将为您提供大量代码来工作。 https://livesql.oracle.com/apex/livesql/file/content_CGRC9SJRBTH83GTAAWUB1H4JG.html

第三,这都是个坏主意。 DML 触发器不应包含 DML 本身。潜在问题和副作用太多。

相反,创建一个包含所有必要逻辑的过程,并让开发人员在执行更新时调用该过程。

【讨论】:

  • 非常感谢您花时间回答这个问题。事实证明,当老师告诉我们包含一条错误消息时,他们的意思是打印一条消息,而不是用一条消息引发异常。在不引发错误的情况下,问题变得微不足道,因为 BEFORE 更新触发器可以将所需的值放入 :NEW.attributeName 并且不会因为没有引发错误而回滚。也感谢您提供的链接,它可能会在作业中的其他问题中派上用场。
  • 很高兴在这里见到你!
【解决方案2】:

事实证明,老师的意思是我们使用 DBMS 输出来显示“错误消息”,而不是实际引发错误。那么解决方案就变成了

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;
/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-02
    • 2018-11-30
    • 2022-10-20
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 2020-05-20
    • 2012-05-17
    相关资源
    最近更新 更多