<六> 触发器
<1>、SQL SERVER端语法说明
1、语法:
CREATE TRIGGER [owner.]trigger_name
ON [owner.]table_name
FOR { INSERT, UPDATE, DELETE }
[WITH ENCRYPTION]
AS
sql_statement [...n]
或者使用IF UPDATE子句:
CREATE TRIGGER [owner.]trigger_name
ON [owner.]table_name
FOR { INSERT, UPDATE }
[WITH ENCRYPTION]
AS
IF UPDATE (column_name)
[{AND | OR} UPDATE (column_name)…]
sql_statement [ ...n]
2、示例:
IF EXISTS (SELECT 1 FROM sysobjects
WHERE name = 'reminder' AND type = 'TR')
DROP TRIGGER reminder
GO
CREATE TRIGGER employee_insupd
ON employee
FOR INSERT, UPDATE
AS
/* Get the range of level for this job type from the jobs table. */
DECLARE @min_lvl tinyint,
@max_lvl tinyint,
@emp_lvl tinyint,
@job_id smallint
SELECT @min_lvl = min_lvl,
@max_lvl = max_lvl,
@emp_lvl = i.job_lvl,
@job_id = i.job_id
FROM employee e, jobs j, inserted i
WHERE e.emp_id = i.emp_id AND i.job = j.job_id
IF (@job_id = 1) and (@emp_lvl <> 10)
BEGIN
RAISERROR ('Job id 1 expects the default level of 10.', 16, 1)
ROLLBACK TRANSACTION
END
ELSE
IF NOT (@emp_lvl BETWEEN @min_lvl AND @max_lvl)
BEGIN
RAISERROR ('The level for job_id:%d should be between %d and %d.',
16, 1, @job_id, @min_lvl, @max_lvl)
ROLLBACK TRANSACTION
END
GO
<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 screen.width/2)this.style.width=screen.width/2;" border=0> 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是没有意义的,因为怎么确