变异表是一个当前正在改变的表,正在执行DML语句的表,改变可以是因为INSERT、UPDATE或DELETE语句,或者由于DELETE CASCADE约束。这种错误类型只会在行级触发器上发生,当表在改变时,不能对表进行查询或修改,如果读取或者修改这个表,就会导致变异表错误。
这里给出一个书中例子,对SECTION表执行INSERT或者UPDATE语句之前,该触发器会检查指定教师是否承担太多的班级的教学任务,如果某个教师所承担班级数量等于或大于10,则触发器会抛出一个错误信息,说明教师所教授班级太多。
1 CREATE OR REPLACE TRIGGER section_biu
2 BEFORE INSERT OR UPDATE ON section
3 FOR EACH ROW
4 DECLARE
5 v_total NUMBER;
6 v_name VARCHAR2(30);
7 BEGIN
8 SELECT COUNT(*)
9 INTO v_total
10 FROM section
11 WHERE instructor_id =:NEW.instructor_id;
12 IF v_total >=10 THEN
13 SELECT first_name||' '||last_name
14 INTO v_name
15 FROM instructor
16 WHERE instructor_id = :NEW.instructor_id;
17 RAISE_APPLICATION_ERROR
18 (-20000,'Instructor, '||v_name||', is overbooked');
19 END IF;
20 EXCEPTION
21 WHEN NO_DATA_FOUND THEN
22 RAISE_APPLICATION_ERROR
23 (-20001,'This is not a valid instructor');
24 END;
当对SECTION表执行下面语句时
UPDATE section
SET instructor_id =101
WHERE section_id =80;
执行该语句后会出现ORA-04091错误。
解决该错误三种方法:
1、利用自治事务
在第5行插入PRAGMA AUTONOMOUS_TRANSACTIONBEGIN
在第24行插入COMMIT
2、利用包和拆分触发器
利用PL/SQL包声明两个全局变量记录教师ID和名字
CREATE OR REPLACE PACKAGE instructor_adm AS
v_instructor_id instuctor.instructor_id%TYPE
v_instructor_name varchar2(40);
END;
修改已有的触发器section_biu,不需要声明部分,执行部分修改为
IF :NEW.instructor_id IS NOT NULL THEN
BEGIN
instructor_adm.v_instructor_id:= :NEW.instructor_id;
SELECT first_name||' '||last_name
INTO instructor_adm.v_instructor_name
FROM instructor
WHERE instructor_id = instructor_adm.v_instructor_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR
(-20001,'This is not a valid instructor');
END:
END IF;
再为section表创建一个新触发器,一个语句级触发器
CREATE OR REPLACE TRIGGER section_aiu
AFTER INSERT OR UPDATE ON section
DECLARE
v_total INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_total
FROM section
WHERE instructor_id = instructor_adm.v_instructor_id;
IF v_total >=10 THEN
RAISE_APPLICATION_ERROR
(-20000,'Instructor, '||instructor_adm.v_instructor_name||', is overbooked');
END IF;
END:
之所以要这样做就是因为保证了表在发生改变完成后去查询,这样才不会出现错误。
3、使用复合触发器
这样可以不需要在包里声明全局变量,只需要在触发器中定义局部变量记录教师ID和名字,然后在复合触发器的BEFORE EACH ROW IS部分和AFTER STATEMENT IS部分,这两部分内容分别与方法2中两个不同触发器相类似。
这里给出一个书中例子,对SECTION表执行INSERT或者UPDATE语句之前,该触发器会检查指定教师是否承担太多的班级的教学任务,如果某个教师所承担班级数量等于或大于10,则触发器会抛出一个错误信息,说明教师所教授班级太多。
1 CREATE OR REPLACE TRIGGER section_biu
2 BEFORE INSERT OR UPDATE ON section
3 FOR EACH ROW
4 DECLARE
5 v_total NUMBER;
6 v_name VARCHAR2(30);
7 BEGIN
8 SELECT COUNT(*)
9 INTO v_total
10 FROM section
11 WHERE instructor_id =:NEW.instructor_id;
12 IF v_total >=10 THEN
13 SELECT first_name||' '||last_name
14 INTO v_name
15 FROM instructor
16 WHERE instructor_id = :NEW.instructor_id;
17 RAISE_APPLICATION_ERROR
18 (-20000,'Instructor, '||v_name||', is overbooked');
19 END IF;
20 EXCEPTION
21 WHEN NO_DATA_FOUND THEN
22 RAISE_APPLICATION_ERROR
23 (-20001,'This is not a valid instructor');
24 END;
当对SECTION表执行下面语句时
UPDATE section
SET instructor_id =101
WHERE section_id =80;
执行该语句后会出现ORA-04091错误。
解决该错误三种方法:
1、利用自治事务
在第5行插入PRAGMA AUTONOMOUS_TRANSACTIONBEGIN
在第24行插入COMMIT
2、利用包和拆分触发器
利用PL/SQL包声明两个全局变量记录教师ID和名字
CREATE OR REPLACE PACKAGE instructor_adm AS
v_instructor_id instuctor.instructor_id%TYPE
v_instructor_name varchar2(40);
END;
修改已有的触发器section_biu,不需要声明部分,执行部分修改为
IF :NEW.instructor_id IS NOT NULL THEN
BEGIN
instructor_adm.v_instructor_id:= :NEW.instructor_id;
SELECT first_name||' '||last_name
INTO instructor_adm.v_instructor_name
FROM instructor
WHERE instructor_id = instructor_adm.v_instructor_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR
(-20001,'This is not a valid instructor');
END:
END IF;
再为section表创建一个新触发器,一个语句级触发器
CREATE OR REPLACE TRIGGER section_aiu
AFTER INSERT OR UPDATE ON section
DECLARE
v_total INTEGER;
BEGIN
SELECT COUNT(*)
INTO v_total
FROM section
WHERE instructor_id = instructor_adm.v_instructor_id;
IF v_total >=10 THEN
RAISE_APPLICATION_ERROR
(-20000,'Instructor, '||instructor_adm.v_instructor_name||', is overbooked');
END IF;
END:
之所以要这样做就是因为保证了表在发生改变完成后去查询,这样才不会出现错误。
3、使用复合触发器
这样可以不需要在包里声明全局变量,只需要在触发器中定义局部变量记录教师ID和名字,然后在复合触发器的BEFORE EACH ROW IS部分和AFTER STATEMENT IS部分,这两部分内容分别与方法2中两个不同触发器相类似。