插入触发器工作正常,但更新触发器会抛出错误,因为我试图访问它的表

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

我需要制作两个单独的触发器(必须以这种方式制作),一个用于插入,一个用于包含项目不同阶段的表的更新,该项目的 id 来自另一个表。每当项目阶段更新时,我需要确保项目阶段的总预算不高于实际项目的预算。这是创建表和触发器之一的代码。

CREATE TABLE Projet (
        id_projet integer PRIMARY KEY,
        nom_projet varchar(50) NOT NULL,
        date_debut date NOT NULL,
        date_fin date NOT NULL,
        budget numeric(10,2) NOT NULL
);

CREATE TABLE Projet_phase (
        id_projet integer,
        id_phase integer UNIQUE,
        nom_phase varchar(50) NOT NULL,
        date_debut date NOT NULL,
        date_fin date NOT NULL,
        budget numeric(10,2) NOT NULL,
        PRIMARY KEY (id_projet, id_phase),
        FOREIGN KEY (id_projet) REFERENCES Projet(id_projet)ON DELETE CASCADE
); 

-- Somme budgets phases < budget projet pour projet_phase
CREATE OR REPLACE TRIGGER T_validerbudget_Insertion_projetphase
BEFORE INSERT ON Projet_phase
FOR EACH ROW
DECLARE
    budget_total NUMBER := 0;
    budget_projet Projet.budget%TYPE;

    
BEGIN
    SELECT NVL((sum(budget) + :NEW.budget), 0) total
        INTO budget_total
        FROM projet_phase
        WHERE id_projet = :NEW.id_projet;
    SELECT budget INTO budget_projet FROM Projet WHERE id_projet = :NEW.id_projet;
    IF budget_total > budget_projet THEN
        RAISE_APPLICATION_ERROR(
        -20001,
        'IMPOSSIBLE D''AJOUTER LA PHASE CAR LA SOMME DES BUDGETS DES PHASES DÉPASSE CELUI DU PROJET'
        );
    END IF;
END;
/

CREATE OR REPLACE TRIGGER T_validerbudget_Insertion_projetphase
BEFORE UPDATE ON Projet_phase
FOR EACH ROW
DECLARE
    budget_total NUMBER := 0;
    budget_projet Projet.budget%TYPE;

    
BEGIN
    SELECT NVL((sum(budget) + :NEW.budget), 0) total
        INTO budget_total
        FROM projet_phase
        WHERE id_projet = :NEW.id_projet AND id_phase <> :NEW.id_phase;
    SELECT budget INTO budget_projet FROM Projet WHERE id_projet = :NEW.id_projet;
    IF budget_total > budget_projet THEN
        RAISE_APPLICATION_ERROR(
        -20001,
        'IMPOSSIBLE DE MODIFIER LA PHASE CAR LA SOMME DES BUDGETS DES PHASES DÉPASSE CELUI DU PROJET'
        );
    END IF;
END;
/

第一个似乎工作完美,但第二个给了我这个错误:

ORA-04091 表 string.string 正在发生变化,触发器/函数可能看不到它

我对一些事情感到非常困惑:

  1. 为什么只有其中一个本身并发出问题,而不是两个都因为这个原因而不起作用
  2. 如果触发器无法访问该表的数据,我该如何测试该表的信息

我尝试在更新后使用触发器,以防事务不会被视为仍在处理中,从而使我可以访问该表,但仍然没有。还尝试添加 PRAGMA AUTONOMOUS_TRANSACTION 因为它经常被提及。然而这并没有改变任何事情。

sql oracle-database triggers
1个回答
0
投票

您有

FOR EACH ROW
触发器。假设您有一个预算为 100 美元的项目,目前有两个阶段的成本分别为 40 美元和 50 美元。现在这两个阶段都变得更加昂贵。您必须为每个项目添加 10 美元:

更新 projet_phase 设置预算 = 预算 + 10;

现在DBMS有一个问题:一期要花费50美元,一期要花费60美元,总和会超出项目预算。但是你告诉它检查每一行的更新并告诉你它是否正常。因此,如果 DBMS 首先将 40$ 更新到 50$,则该行没问题,但另一行将被拒绝。但是,如果 DBMS 首先更新 50$ 到 60$,则该行没问题,并且 40$ 到 50$ 的更新将被拒绝。这正是 DBMS 告诉您的:它无法应用触发器并告诉您更新语句运行的哪些行更新是被禁止的,因为表正在更改。

出于这个原因,DBMS 将不允许对表上的任何更新语句进行更新触发器。这与插入触发器和 INSERT VALUES 语句不同,因为该语句只能插入一行,因此 DBMS 认为没有问题。但是,对于 INSERT SELECT 语句,您会得到相同的错误。

演示:https://dbfiddle.uk/anNF8HYt

最简单的解决方案是创建一个后置触发器。这主要意味着省略

FOR EACH ROW
子句。因此,更新/插入语句将遍历所有行,然后触发器才会检查情况。

CREATE OR REPLACE TRIGGER t_validerbudget_projetphase
AFTER INSERT OR UPDATE OF budget ON projet_phase
DECLARE
  v_count INT;
BEGIN
  -- Count the projects with exceeded budget.
  SELECT COUNT(*)
  INTO v_count
  FROM
  (
    SELECT id_projet
    FROM projet p
    JOIN projet_phase pp USING (id_projet)
    GROUP BY id_projet
    HAVING MIN(p.budget) < SUM(pp.budget)
  );

  -- If such projects exist now, throw an exception.  
  IF v_count > 0 THEN
    RAISE_APPLICATION_ERROR(
      -20001,
      'La somme des budgets des phases ne doit pas dépasser celle du projet'
    );
  END IF;
END t_validerbudget_projetphase;
/

演示:https://dbfiddle.uk/TXWMcV__

© www.soinside.com 2019 - 2024. All rights reserved.