Plsql PL/SQL触发器获取一个变异表错误
我的触发器想检查一位“新”经理是否管理不超过5名员工。 经理管理表中只有5人,员工人数。 最后,每次更新都记录在SUPERLOG tabledate、user、old_manager、new_manager中。 我没有收到有关触发器的编译错误,但当我更新Supersn时,我收到以下错误:Plsql PL/SQL触发器获取一个变异表错误,plsql,triggers,oracle10g,mutating-table,Plsql,Triggers,Oracle10g,Mutating Table,我的触发器想检查一位“新”经理是否管理不超过5名员工。 经理管理表中只有5人,员工人数。 最后,每次更新都记录在SUPERLOG tabledate、user、old_manager、new_manager中。 我没有收到有关触发器的编译错误,但当我更新Supersn时,我收到以下错误: SQL> update employee set superssn='666666607' where ssn='111111100'; update employee set superssn='666
SQL> update employee set superssn='666666607' where ssn='111111100';
update employee set superssn='666666607' where ssn='111111100'
*
ERROR at line 1:
ORA-04091: Table FRANK.EMPLOYEE is mutating, the trigger/function
can't read it
ORA-06512: a "FRANK.TLOG", line 20
ORA-04088: error during execution of trigger 'FRANK.TLOG'
我怎样才能解决这个问题?多谢各位
create or replace trigger tlog
before update of superssn on employee
for each row
declare
t1 exception;
n number:=0;
cont number:=0;
empl varchar2(16);
cursor cur is (select ssn from blocked_manager where ssn is not null);
begin
open cur;
loop
fetch cur into empl;
exit when cur%notfound;
if(:new.superssn = empl) then
n:=1;
end if;
end loop;
close cur;
if n=1 then
raise t1;
end if;
select count(*) into cont from employee group by superssn having superssn=:new.superssn;
if(cont=4) then
insert into blocked_manager values(:new.superssn,5);
end if;
insert into superlog values(sysdate,user,:old.superssn, :new.superssn );
exception
when t1 then
raise_application_error(-20003,'Manager '||:new.superssn||' has already 5 employees');
end;
正如您所发现的,您不能从定义行级触发器的同一个表中进行选择;它会导致表突变异常 为了使用触发器正确创建此验证,应创建一个过程以获取用户指定的锁,以便在多用户环境中正确序列化验证
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
然后,此过程可以用于复合触发器(假设至少是Oracle 11),在早期版本中,需要将其拆分为单个触发器
CREATE OR REPLACE TRIGGER too_many_employees
FOR INSERT OR UPDATE ON employee
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal employee supervisor table
g_superssns := sys.odcivarchar2list();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
g_superssns.EXTEND;
g_superssns(g_superssns.LAST) := :new.superssn;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
解决这个问题的最快方法可能是使用精心构造的语句触发器,而不是行触发器。行触发器中的每一行都有相应的短语,根据触发器上的BEFORE/AFTER INSERT、BEFORE/AFTER UPDATE和BEFORE/AFTER DELETE约束修改的每一行都会调用行触发器,可以看到相应的:NEW和:OLD值,并且受“不能查看定义触发器的表”规则的约束。语句触发器在执行的每个语句的适当时间调用,这些语句看不到行值,但不受查看定义它们的特定表的限制。因此,对于不需要使用:NEW或:OLD值的逻辑部分,这样的触发器可能会很有用:
CREATE OR REPLACE TRIGGER EMPLOYEE_S_BU
BEFORE UPDATE ON EMPLOYEE
-- Note: no BEFORE EACH ROW phrase, so this is a statement trigger
BEGIN
-- The following FOR loop should insert rows into BLOCKED_MANAGER for all
-- supervisors which have four or more employees under them and who are not
-- already in BLOCKED_MANAGER.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
LEFT OUTER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
WHERE b.SSN IS NULL
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) >= 4)
LOOP
INSERT INTO BLOCKED_MANAGER
(SSN, EMPLOYEE_COUNT)
VALUES
(aRow.SUPERSSN, aRow.EMP_COUNT);
END LOOP;
-- Remove rows from BLOCKED_MANAGER for managers who supervise fewer
-- than four employees.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
INNER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) <= 3)
LOOP
DELETE FROM BLOCKED_MANAGER
WHERE SSN = aRow.SUPERSSN;
END LOOP;
-- Finally, if any supervisor has five or more employees under them,
-- raise an exception. Note that we go directly to EMPLOYEE to determine
-- the number of employees supervised.
FOR aRow IN (SELECT SUPERSSN, COUNT(*) AS EMP_COUNT
FROM EMPLOYEE
GROUP BY SUPERSSN
HAVING COUNT(*) >= 5)
LOOP
-- If we get here we've found a supervisor with 5 (or more) employees.
-- Raise an exception
RAISE_APPLICATION_ERROR(-20000, 'Found supervisor ' || aRow.SUPERSSN ||
' supervising ' || aRow.EMP_COUNT ||
' employees');
END LOOP;
END EMPLOYEE_S_BU;
请注意,如果您去掉了这个触发器仍然保持的阻塞的_MANAGER表,尽管我不知道是否真的有必要,但是逻辑会大大减少
您仍然需要一个行触发器来处理日志记录,但由于这只是减少现有触发器的问题,我将把它留给您:-
共享和享受。此解决方案不涉及并发控制,因此,通过多个会话,可以让一名主管管理5名以上的员工。如果添加了并发控制,则使用此方法需要序列化对整个employees表的访问,这可能会导致可伸缩性问题。
CREATE OR REPLACE TRIGGER EMPLOYEE_S_BU
BEFORE UPDATE ON EMPLOYEE
-- Note: no BEFORE EACH ROW phrase, so this is a statement trigger
BEGIN
-- The following FOR loop should insert rows into BLOCKED_MANAGER for all
-- supervisors which have four or more employees under them and who are not
-- already in BLOCKED_MANAGER.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
LEFT OUTER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
WHERE b.SSN IS NULL
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) >= 4)
LOOP
INSERT INTO BLOCKED_MANAGER
(SSN, EMPLOYEE_COUNT)
VALUES
(aRow.SUPERSSN, aRow.EMP_COUNT);
END LOOP;
-- Remove rows from BLOCKED_MANAGER for managers who supervise fewer
-- than four employees.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
INNER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) <= 3)
LOOP
DELETE FROM BLOCKED_MANAGER
WHERE SSN = aRow.SUPERSSN;
END LOOP;
-- Finally, if any supervisor has five or more employees under them,
-- raise an exception. Note that we go directly to EMPLOYEE to determine
-- the number of employees supervised.
FOR aRow IN (SELECT SUPERSSN, COUNT(*) AS EMP_COUNT
FROM EMPLOYEE
GROUP BY SUPERSSN
HAVING COUNT(*) >= 5)
LOOP
-- If we get here we've found a supervisor with 5 (or more) employees.
-- Raise an exception
RAISE_APPLICATION_ERROR(-20000, 'Found supervisor ' || aRow.SUPERSSN ||
' supervising ' || aRow.EMP_COUNT ||
' employees');
END LOOP;
END EMPLOYEE_S_BU;