Oracle 触发器
<六> 触发器
<2>、ORACLE端语法说明
1、语法:
CREATE [OR REPLACE] TRIGGER trigger_name
{ BEFORE | AFTER } triggering_event ON table_name
[ FOR EACH ROW ]
[ WHEN trigger_condition ]
trigger_body ;
2、使用说明与示例:
(1)、上语法中,
trigger_event 是对应于DML的三条语句INSERT、UPDATE、DELETE;
table_name是与触发器相关的表名称;FOR EACH ROW是可选子句,当使用时,对每条相应行将引起触发器触发;
condition是可选的 ORACLE BOOLEAN条件,当条件为真时触发器触发;
trigger_body是触发器触发时执行的PL/SQL块。
(2)、ORACLE触发器有以下两类:
1> 语句级(Statement-level)触发器,在CREATE TRIGGER语句中不包含FOR EACH ROW子句。语句级触发器对于触发事件只能触发一次,
而且不能访问受触发器影响的每一行的列值。一般用语句级触发器处理有关引起触发器触发的SQL语句的信息——例如,由谁来执行
和什么时间执行。
2> 行级(Row-level)触发器,在CREATE TRIGGER语句中包含FOR EACH ROW子句。行级触发器可对受触发器影响的每一行触发,并且能
够访问原列值和通过SQL语句处理的新列值。行级触发器的典型应用是当需要知道行的列值时,执行一条事务规则。
(3)在触发器体内,行级触发器可以引用触发器触发时已存在的行的列值,这些值倚赖于引起触发器触发的SQL语句。
1> 对于INSERT语句,要被插入的数值包含在new.column_name,这里的column_name是表中的一列。
2> 对于UPDATE语句,列的原值包含在old.column_name中,数据列的新值在new.column_name中。
3> 对于DELETE语句,将要删除的行的列值放在old.column_name中。
触发语句 :old :new
INSERT 无定义——所有字段都是NULL 当该语句完成时将要插入的数值
UPDATE 在更新以前的该行的原始取值 当该语句完成时将要更新的新值
DELETE 在删除行以前的该行的原始取值 未定义——所有字段都是NULL
4> 在触发器主体中,在new和old前面的“:”是必需的。而在触发器的WHEN子句中,:new和:old记录也可以在WHEN子句的condition内部
引用,但是不需要使用冒号。例如,下面CheckCredits触发器的主体仅当学生的当前成绩超过20时才会被执行:
CREATE OR REPLACE TRIGGER CheckCredits
BEFORE INSERT OR UPDATE OF current_credits ON students
FOR EACH ROW
WHEN (new.current_credits > 20)
BEGIN
/*Trigger body goes here. */
END ;
但CheckCredits也可以按下面方式改写:
CREATE OR REPLACE TRIGGER CheckCredits
BEFORE INSERT OR UPDATE OF current_credits ON students
FOR EACH ROW
BEGIN
IF :new.current_credits > 20 THEN
/*Trigger body goes here. */
END IF ;
END ;
注意: WHEN子句仅能用于行级触发器,如果使用了它,那么触发器主体仅仅对那些满足WHEN子句指定的条件的行进行处理。
(4)触发器的主体是一个PL/SQL块,在PL/SQL块中可以使用的所有语句在触发器主体中都是合法的,但是要受到下面的限制:
1> 触发器不能使用事务控制语句,包括COMMIT、ROLLBACK或SAVEPOINT。
ORACLE保持这种限制的原因是:
如果触发器遇到错误时,由触发器导致的所有数据库变换均能被回滚(roll back)取消;
但如果触发器确认(commit)了对数据库进行的部分变换,ORACLE就不能完全回滚(roll back)整个事务。
2> 在触发器主体中调用到的存储过程的实现语句里也不能使用事务控制语句。
3> 触发器主体不能声明任何LONG或LONG RAW变量。而且,:new和:old不能指向定义触发器的表中的LONG或LONG RAW列。
4> 当声明触发器的表中有外键约束时,如果将定义触发器的表和需要作为DELETE CASCADE参考完整性限制的结果进行更新的表称为变
化表,将外键相关联的表称为限制表,则在此触发器主体中的SQL语句不允许读取或修改触发语句的任何变化表,也不允许读取或修
改限制表中的主键、唯一值列或外键列。
(5)以下是建立一个事前插入触发器的示例:
CREATE OR REPLACE TRIGGER Credit_Charge_Log_Ins_Before
BEFORE insert ON Credit_Charge_Log
FOR EACH ROW
DECLARE
Total_for_past_3days number ;
BEGIN
— Check the credit charges for the past 3 days.
— If they total more than $1000.00, log this entry
— int the Credit_Charge_Attempt_Log for further handling.
select sum ( amount ) into total_for_past_3days
from Credit_Charge_Log
where Card_Number = :new.Card_Number and Transaction_Date >= sysdate – 3;
IF total_for_past_3days > 1000.00 THEN
insert into credit_Charge_Attemp_Log(Card_Number, Amount, Vendor_ID, Transaction_Date)
values (:new.Card_Number, :new.Amount,:new.Vendor_ID, :new.Transaction_Date);
END IF ;
END ;
<3>、从SQL SERVER向ORACLE的迁移方案
1、通过比较上面SQL语法的不同并考虑现有SQL SERVER的实际编程风格,在从T-SQL向PL/SQL迁移时,要遵守下面规则:
1> 在CREATE TRIGGER定义中采用AFTER关键字,即调整为事后触发器。
2> 在CREATE TRIGGER定义中采用FOR EACH ROW关键字,即调整为行级触发器。
3> 将触发器主体中的“inserted”调整为“:new”,将“deleted”调整为“:old”。
4> 在触发器主体中禁用CURSOR操作:new与:old。
5> 在触发器主体中禁用COMMIT、ROLLBACK、SAVEPOINT等事务控制语句。
2、用触发器解决ID列向SEQUENCE迁移的问题:
下面的GenerateStudentID触发器使用了:new。这是一个before INSERT触发器,其目的是使用student_sequence序列所产生的数
值填写students表的ID字段。
例:
CREATE OR REPLACE TRIGGER GenerateStudentID
BEFORE INSERT ON students
FOR EACH ROW
BEGIN
SELECT student_sequence.nextval INTO :new.ID FROM dual;
END;
在上面的触发器主体中,GenerateStudentID实际上修改了:new.ID的值。这是:new最有用的一个特性——当该语句真正被执行时,
:new中的存储内容就将被使用。有了这个触发器,我们就可以使用下面这样的INSERT语句,而不会产生错误:
INSERT INTO students (first_name, last_name) VALUES (‘LUO’, ‘TAO’) ;
尽管我们没有为主键列ID(这是必需的)指定取值,触发器将会提供所需要的取值。事实上,如果我们为ID指定了一个取值,它也将
会被忽略,因为触发器修改了它。如果我们使用下面的语句:
INSERT INTO students (ID, first_name, last_name) VALUES (-789, ‘LUO’, ‘TAO’) ;
其处理结果还是相同的。无论在哪种情况下,student_sequence.nextval都将用作ID列值。
由此讨论,可以采用这种方法处理SQL SERVER中ID列向ORACLE的SEQUENCE转换的问题。
另外,由于上面的原因,我们不能在after行级触发器中修改 :new,因为该语句已经被处理了。通常,:new仅仅在before行级触发器
中被修改,而:old永远不会被修改,仅仅可以从它读出数据。
此外,:new和:old记录仅仅在行级触发器内部是有效的。如果试图要从语句级触发器进行引用,将会得到一个编译错误。因为语句级
触发器只执行一次——尽管语句要处理许多行——所以:new和:old是没有意义的,因为怎么确定它们引用的会是哪一行呢?