我需要制作两个单独的触发器(必须以这种方式制作),一个用于插入,一个用于包含项目不同阶段的表的更新,该项目的 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 正在发生变化,触发器/函数可能看不到它
我对一些事情感到非常困惑:
我尝试在更新后使用触发器,以防事务不会被视为仍在处理中,从而使我可以访问该表,但仍然没有。还尝试添加 PRAGMA AUTONOMOUS_TRANSACTION 因为它经常被提及。然而这并没有改变任何事情。
您有
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;
/