我的触发器想要检查是否有新的'经理监督不超过5名员工。 只监督5人的经理在BLOCKED_MANAGER表(ssn,numberofemployees)中。 最后,每个更新都记录在SUPERLOG表中(date,user,old_manager,new_manager)。 我没有得到关于触发器的编译错误,但是当我更新一个superssn时,我得到了这个错误:
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;
答案 0 :(得分:0)
正如您所发现的那样,您无法从同一个表中选择定义行级触发器;它导致表变异异常。
为了使用触发器正确创建此验证,应创建一个过程以获取用户指定的锁,以便在多用户环境中正确地序列化验证。
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;
您不需要blocked_manager
表来管理此约束。此信息可以从employee
表中派生。
或者在Oracle 11i之前的版本中:
CREATE OR REPLACE PACKAGE employees_trg
AS
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
END employees_trg;
CREATE OR REPLACE TRIGGER employee_biu
BEFORE INSERT OR UPDATE ON employee
IS
BEGIN
-- Reset the internal employee supervisor table
employees_trg.g_superssns := sys.odcivarchar2list();
END;
CREATE OR REPLACE TRIGGER employee_aiur
AFTER INSERT OR UPDATE ON employee
FOR 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
employees_trg.g_superssns.EXTEND;
employees_trg.g_superssns(employees_trg.g_superssns.LAST) := :new.superssn;
END IF;
END;
CREATE OR REPLACE TRIGGER employee_aiu
AFTER INSERT OR UPDATE ON employee
IS
DECLARE
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(employees_trg.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;
答案 1 :(得分:0)
可能最快的方法是使用精心构造的语句触发器而不是行触发器。行触发器中包含短语FOR EACH ROW
,为每个被修改的行调用(基于触发器上的BEFORE/AFTER INSERT
,BEFORE/AFTER UPDATE
和BEFORE/AFTER DELETE
约束),看到合适的:新的和:旧的值,并且要看&#34;不能查看定义触发器的表&#34;规则。语句触发器在适当的时间为每个执行的语句调用,不能查看行值,但不受限于查看它们所在的特定表的限制#39;重新定义。因此,对于您不需要使用的逻辑部分: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;
请注意,如果你摆脱了BLOCKED_MANAGER表(这个触发器仍然存在,虽然我不知道它是否真的有必要),逻辑会大大减少。
您仍然需要行触发来处理日志记录,但由于这只是减少现有触发器的问题,我会留给您。 : - )
分享并享受。