INSTEAD OF与AFTER触发器

INSTEAD OF 触发器
  AFTER 触发器(也叫“FOR”触发器)会在触发 insert、update 或是delect 动作之后执行。例如,一个 Employees 表上的 AFTER 触发器会在在 Employee 表上执行一条 update 语句后激活。因此,AFTER 触发器只有在已插入一行或是多行和所有约束已被处理且通过后才触发。INSTEAD OF 触发器和 AFTER 触发器有本质上的不同,因为 INSTEAD OF 触发器代替触发动作进行激发。就拿同样的例子来说,如果在 Emplyees 表上有一个 INSTEAD OF UPDATE 触发器和在这个表上执行一条 UPDATE 语句,结果是这条 UPDATE 语句并不会改变 Employee 表中的任何一行。相反,这条 UPDATE 语句只有是为了触发 INSTEAD OF UPDATE 触发器,这个触发器可能会,也可能不会改变 Employees 表中的数据。
  因此,怎么决定在合适的时间和位置放置 INSTEAD OF 触发器呢?有几个关键的因素在做决定是值得考虑的。AFTER 触发器多用在动作必须在表中数据发生改变之后才执行后情情况。比如,AFTER 触发器可以用于将对数据作任何变动的日志记录在一个相对独立的审计表中。INTEAD OF 触发器也能做同样的工作。但是 INSTEAD OF 触发器在这个情况下的效率比较低,因为更新动作只能在将它发生的动作准确地记录在审计表之后才允许执行。
  一般来说,只要不影响数据的修改,AFTER 触发器比 INSTEAD OF 触发器更有效率。在对数据进行计算或是对数据的修改作为一个整体提交或是作为一个整体回退的情况下,AFTER 触发器也是一个很好的选择。例如,存在这样一条规则:对在 Products 表的产品价格的变动超过30%的必须回退。AFTER 触发器能很漂亮地完成这个工作,它利用已插入同已删除的表中的产品价格作比较,然后在有必要的时回滚事务。这些都是 AFTER 触发器的理想条件,但有时 INSTEAD OF会更好些。
  INSTEAD OF 触发器有一个很大的特点——就是它允许你在某个表或视图上用多个复杂的查询操作来代替单一的查询。跟 AFTER 触发器只能对表起作用不同,INSTEAD OF 触发器可以同时对表和视图起作用。我常常被问到怎么样去解决这种情况:有一个多表组成的视图,如何对该视图进行一次更新。如果视图包含有关键字段和包含有基本表的某些字段,这只是简单的更新基本表。但是,当有视图中包含有多个基本表示,逻辑上的更新比单单一个 UPDATE 语句会更复杂。因此,你是怎么利用什么可以替代的工具来解决这个问题的呢?其中一个方法就是将一个INSTEAD OF 触发器放在视图上。INSTEAD OF 触发器可以定义在一个或多个表上.INSTEAD OF 触发器就能转开在多个基本表中修改的范围.
  例如,如果一个视图将 Customers、Products、orders 和 OrderDteils 等表合并成一个视图,并利用视图通过程序在屏幕上来显示所有的数据。更新操作便允许用来代替这个视图,假如存在一个这个样的视图:它包含 Northwind 数据库中的四个表,并且被命名为vwCustomersOrdersOrderDetailsProducts,它看起来像这样(Figure 1):

Figure 1 连接 Customers 及其 Order Details 的视图

CREATE VIEW vwCustomersOrdersOrderDetailsProducts
AS
    SELECT    c.CustomerID, 
        c.CompanyName, 
        o.OrderID, 
        o.OrderDate, 
        od.UnitPrice, 
        od.Quantity, 
        od.Discount, 
        p.ProductID, 
        p.ProductName
    FROM Customers c
        INNER JOIN Orders o ON c.CustomerID = o.CustomerID 
        INNER JOIN [Order Details] od ON o.OrderID = od.OrderID 
        INNER JOIN Products p ON od.ProductID = p.ProductID
GO      
  vwCustomersOrdersOrderDetailsProducts 视图连接着四个表,并且每个表都暴露一个取样字段。必须记住的一点是,当你设计一个含有 INSTEAD OF UPDATE 的触发器时,将每个表的主关键字段包含在SELECT语句中是很有益的做法。即使这些字段在应用程序不会用到,它们也以在 INSTEAD OF 触发器中用来定位将要被修改的行,然后对基表作相应的修改。假设你打算允许更新该视图以便按非关键字过滤基表。更新代码应该写在 INSTEAD OF UPDATE 触发器中,让触发器去更新 Customers 表中的 CompnayName 列,Orders 表中的 OrderDate 列,Order Details 表的 UnitPrice 和 Quantity 列以及在 Products 表中的 ProductName 列。在这种情况下,使用 AFTER 触发器就不适合了,而 INSTEAD OF 触发器则是一个很好的选择,参见 Figure 2: Figure 2 用 INSTEAD OF 触发器更新视图

CREATE TRIGGER tr_vwCustomersOrdersOrderDetailsProducts_IO_U 
    ON vwCustomersOrdersOrderDetailsProducts
    INSTEAD OF UPDATE
AS
    — 更新 Customers
    UPDATE     Customers
    SET    CompanyName = i.CompanyName 
    FROM    inserted i 
        INNER JOIN Customers c ON i.CustomerID = c.CustomerID

— 更新 Orders
    UPDATE     Orders
    SET    OrderDate = i.OrderDate
    FROM    inserted i 
        INNER JOIN Orders o ON i.OrderID = o.OrderID

— 更新 Order Details
    UPDATE     [Order Details]
    SET    UnitPrice = i.UnitPrice,
        Quantity = i.Quantity
    FROM    inserted i 
        INNER JOIN [Order Details] od ON i.OrderID = od.OrderID AND 
            i.ProductID = od.ProductID

— 更新 Products
    UPDATE     Products
    SET    ProductName = i.ProductName
    FROM    inserted i 
        INNER JOIN Products p ON i.ProductID = p.ProductID
GO      
  注意在 Figure 2 中的 INSTEAD OF UPDATE 触发器包含了四个 UPDATE 语句。每个 UPDATE语句目的都是为了对其中一个基表中的非关键字段进行修改。在 UPDATE 语句中包含了每个表中的关键字段对应于视图中的字段。这样就允许 UPDATE 语句在相应的表中定位对应的列并只对这些列作修改。下面的 UPDATE 语句将对 INSTEAD OF 触发器进行测试: UPDATE   vwCustomersOrdersOrderDetailsProducts 
SET    Quantity = 100,
       UnitPrice = 20,
       CompanyName = ‘‘‘‘Fake Name‘‘‘‘,
       OrderDate = ‘‘‘‘11/23/2001‘‘‘‘,
       ProductName = ‘‘‘‘Widget‘‘‘‘
WHERE    OrderID = 10265
AND      ProductID = 17        
  如果你(通过视图或是表自身)检查相应表中的值,很明显,这些值已被更新了。当然,对INSTEAD OF 触发器作一些改变会使其有不同的结果。例如,不存在写一个触发器去改变四个基表的需求,因此,可以将触发器中的一个或是多个 UPDATE 语句删去。假设 INSTEAD OF 触发器仅仅是为了更新 Order Details 表的值,这就会仅仅更新在 Order Details 表中的字段,而忽视任何在其他基表上的修改。在这种情况下,在 Customers,Products 或是 Orders 表中不会产生任何错误同时也不会发生任何改变。当然,如果这三个表中的某些字段发生改变的话,会发生报错。如我呆会在这篇文章会讨论的一样,UPDATE 和 COLUMNS_UPDATED 函数是个检测哪些字段发生改变的理想的方法。
  Figure 2 也演示了怎么写一个触发器修改多行记录。注意到 UPDATE 语句如何按关键字连接被插入的表和各个基表。这就保证更新是对所有的行,这些行在视图中被原有的 UPDATE 语句修改。通过循环被插入表的记录行也能完成该操作。不管怎么样,通常避免使用游标是个好主意,尤其是在使用触发器时更应如此。SQL SERVER 被设计成以数据集的方式来处理数据,而游标是为一次处理一个数据行而设计的。在触发器中使用游标会降低程序的性能,因此,最好能使用象 Figure 2 中那样更有效代替方法或使用一个子查询。
  另一个改变 INSERT OF UPDATE 触发器的方法就是使其在视图的 INSERT 和 DELETE 语句中激发。这也就意味着在适当的地方,触发器会实现 INSERT 或是 DELETE 的功能。但是必须记隹的是 DELETE 可能会删除多个记录,这关键在于触发器是怎样写的。因此,检查触发器的需求,在实现之前进行测试,这些做法十分重要。INSERT OF UPDATE 触发器可写在视图中,因此它可插入一个新的顾客、订单、详细的订单和产品。这个触发器也可以用来在插入一个新顾客之前检查这个顾客是否是新的(对其它记录的操作也是一样)。当采用的是 INSTEAD OF 触发器时存在有许多机会,但是,当然,触发器是为解决相应的需求这才是它的本质。
  通常,当引用一张表的 UPDATE 语句试图去赋值一个计算型的,恒等型的或是时间戳型的列时,会产生一个错误,因为这些列的值必须是由SQL SERVER来决定的。这些列必须被包含在UPDATE 语句中以便能满足列不能为空的要求。但是,如果 UPDATE 语句用 INSTEAD OF 触发器引用一个视图,定义在触发器中的逻辑可以旁路掉这些列来避免错误的发生。为了达到这个目的,触发器决不能尝试去更新基表中相应列的值(让它们远离 UPDATE 语句的 SET 从句)。当某一条被处理的记录来自被插入的表时,计算型的,恒等型的或是时间戳型的列可以用一个虚假值以满足不为空值的要求,这时,INSTEAD OF 触发器将忽略这些值,正确的值由 SQL SERVER 设置。

更新分开的列 
  INSTEAD OF 触发器也很普遍地用于更新基表中计算型的列。例如,假设存在有如下这样一个叫 vwOrdersOrderDetailsProducts 的视图: CREATE VIEW vwOrdersOrderDetailsProducts
AS
    SELECT    o.OrderID,
        o.OrderDate,
        od.UnitPrice * od.Quantity AS ExtendedPrice,
        p.ProductID, 
        p.ProductName
    FROM Orders o
        INNER JOIN [Order Details] od ON o.OrderID = od.OrderID 
        INNER JOIN Products p ON od.ProductID = p.ProductID
GO

  这个视图揭示了一个计算型的列叫ExtendedPrice,这个列不能被直接被更新,因为它不能将其自己变为表中独立的一列。虽然你可实现这样一个生意规则,在这个规则中ExtendedPrice通过这个视图来修改,Quantity列不应修改,但是UnitPrice可被修改(我知道这条规则有点奇怪,但我可以忍受这点)。可以写一个INSTEAD OF UPDATE触发器来增强这条生意规则,其代码如下所示: CREATE TRIGGER tr_vwOrdersOrderDetailsProducts_IO_U 
    ON vwOrdersOrderDetailsProducts
    INSTEAD OF UPDATE
AS
    UPDATE     [Order Details]
    SET        UnitPrice = i.ExtendedPrice / Quantity
    FROM       inserted i 
        INNER JOIN [Order Details] od ON i.OrderID = od.OrderID AND 
               i.ProductID = od.ProductID

GO

  这些代码揭示了怎样用一个在INSTEAD OF 触发器中的逻辑来代替对一个计算型列的更新。假设一个产品在一张特定的定单表中Quantity为100而ExtendedPrice要更新为200,这时新的UnitPrice值就变为2。在这种情况下,在执行一个对ExtendedPrice列进行修改的UPDATE语句时,最终的结果是UnitPrice被赋为ExtendedPrice除以Quantity的商。下面的代码可以用来测试这种情况: UPDATE   vwOrdersOrderDetailsProducts
SET      ExtendedPrice = 200
WHERE    OrderID = 10265
AND      ProductID = 17

检查改变
  在INSTEAD OF和AFTER触发器中都有UPDATE和COLUMNS_UPDATE功能,这二种功能允许由触发器决定哪些字段由触发器的语句来改变。例如,下面的触发器阻止任何对Employees表中的lastname字段进行修改。在这里,UPDATE功能用来决定对哪些对字段的修改可以执行。如果超出发生了改变(而又是不允许修改的)就会产生一个错误。PAISERR OR功能和事务就会回退,回退会撤消所做的任何修改。UPDATE功能都可以在AGTER触发器和INSTEAD OF触发器中工作,而不是在外部工作。 CREATE TRIGGER tr_Employees_U on Employees AFTER UPDATE AS
    IF UPDATE(lastname)
    BEGIN
        RAISERROR (‘‘‘‘cannot change lastname‘‘‘‘, 16, 1)
        ROLLBACK TRAN
        RETURN
    END
GO

   UPDATE功能是为了判断单一列是否被INSERT或是UPDATE语句修改过。UPDATE(列)是一个用来检测更新的标准的方法。但是当需要用来他检测多列是否受到INSERT或UPDATE语句的影响时就变得更低效率。而这恰恰是COLUM_UPDATE功能的一个亮点。COLUMN_UPDATE功能返回一个位掩码来判断特定的列是否被修改过。位掩码是包含在被表中被修改的列中的一个比特,目的是为了在表模式中定义这些列。如果一行修改,这比特位的值就为1,否则为0。不像从右到左地读字节的常规方法,位掩码是从左往右读。例如,下面的代码提示了一个在Order Details表中的触发器,这个触发器是为了检测Quantity和UnitPrice二个字段是否被修改过。 CREATE TRIGGER tr_OrderDetails ON [Order Details] AFTER UPDATE
AS
    IF (COLUMNS_UPDATED() = 12)
    BEGIN
        RAISERROR (‘‘‘‘Cannot change both UnitPrice and Quantity at the 
                    same time‘‘‘‘, 16, 1)
        ROLLBACK TRAN
    END
GO

  如果这个字段都被修改了,就会产生一个错误,同时事务也将回滚。就拿Order Details表来说,COLUMN_UPDATED功能返回代表Order Details表中字段的五个字节。只要第三和第四个字段被修改,上面这种情况就会发生,它检测这些位是不是已赋值为1.当第三和第四位都打开的庆,它就如:00110。L因这个位掩码代表2次幂,第一位表示1,第二位表示2,第三位表示4,第四位表示8,第五位表示16(是的,这是和正常二进制数相反的顺序);因此只表示UnitPrice和Quantity字段被修改位掩码的值为00110,这个值为12(4+8)。请注意,这个触发器只有在UnitPrice和Quantity字段被修改才会将事务回滚。如果其他字段修改的话,位掩码就会不一样,因此就不等于整数12了。如果触发器被修改为禁止对这二个字段修改即使对其他字段也禁止,它就可重新编写为如下: ALTER TRIGGER tr_OrderDetails ON [Order Details] AFTER UPDATE
AS
    IF (COLUMNS_UPDATED() >= 12)
    BEGIN
        RAISERROR (‘‘‘‘Cannot change both UnitPrice and Quantity 
                   at the same time‘‘‘‘, 16, 1)
        ROLLBACK TRAN
    END
GO

  请注意 COLUMN_UPDATED 功能现在是怎么去检测位掩码的但是否小于等于12.如果你修改联系UnitPrice,Quantity和Discount列的话,位掩码就变为00111,代表整数28(4+8+16)。当在一个表中不止有8个列时,这个函数就会先返回包含了前八列的五个字节,而第从第九到第十六就会在第二个字节中,以此类推。这个功能在决定允许哪些列可以被更新比只对第列进行更新的UPDATE功能更有用。
  如前期所描述的一样,在潢足特定条件规则条件下,触发器可以回滚事务,当一个含有回滚的触发器在SQL脚本中执行时,整个处理将被取消。因此,被触发动作修改的的所有数据将由ROLLBACK TRANSACION语句回滚。虽然一个回滚并不阻止触发执行SQL语句所有在ROLLBACK TRANSACION语句后面的语句都会被执行 。特别是当一个触发器继续执行回滚语句后面的语句时,在回滚以后所作的任何修改都不会回滚。发生这种情况是因为当在触发器中执行了一个ROLLBACK TRANSACTION时,所以有的事务都被取消。因此当一个新的查询语句被执行时,一个新的不同与以前事务的事务就重新开始。因此,一般情况下,建议不要在ROLLBACK TRANSACTION语句后放置任何语句。
  像回滚不会自动退出触发器一样,它也不会自动产生错误。如果必须回滚且必会产生错误,PAISERR OR语句应该放在退出触发器代码前,紧跟在回滚后.

结束语 
  在对同一个表的数据所作的修改会激发同样的INSTEAD OF触发器,这种触发器不会递归调用。因此,如果在Emplyee表中有一个INSTEAD OF触发器,P这个触发器是用来更新Employee表的,这并不会发生调用同一个INSTEAD OF触发器。如果允许这种递旭的话,更新应该被禁止。INSTEAD OF触发器和AFTER触发器的另一个不同在于Text,Ntext和Image列可以出现在被更新和删除的表的触发器中。这些二进制列会以如VARCAHAR数值出现在更新和删除表的触发器中,这种是可行的,但这并不是他们原始的数据类型。
  这有一个有用的存储过程-sp_helptrigger 系统存储过程-来检测触发器。他返回定义在表上的触发器类型,这个表是传递给存储过程的。用这种方法,你可以看到哪些触发器和某个表有关联,什么操作动触发这些触发器和判断触发器是AFTER触发器还是INSTEAD OF触发器。 
  在最后二栏中,我已经讨论了AFTER触发器和INSTEAD OF触发器的多个方面。当然,还有许多情形下他们很有用,还有许多使用时机也没有提出来。当一个触发器必须查询其他表的情况下,触发器就会没有什么效率了。在这些情况下,触发器的性能和触发动作会受到很大损害。当使用得好时,触发器是一个很棒的工具,但是必须保证在使用他们之前必须对你的程序作一个全面的测试。

时间: 2024-11-05 12:31:58

INSTEAD OF与AFTER触发器的相关文章

Oracle数据库——触发器的创建与应用

一.涉及内容 1.理解触发器的概念.作用和类型. 2.练习触发器的创建和使用. 二.具体操作 (实验) 1.利用触发器对在scott.emp表上执行的DML操作进行安全性检查,只有scott用户登录数据库后才能向该表中执行DML操作.(第1题中,user是系统函数,返回当前用户.字符串中使用两个单引号表示一个单引号.) 要求:分别以system用户和scott用户对emp 表执行DML操作,试验触发器的运行效果. (1)在scott用户下创建触发器 语句: create or replace t

Mysql中的触发器

阅读目录 什么是触发器 特点及作用 例子:创建触发器,记录表的增.删.改操作记录 弊端 什么是触发器 简单的说,就是一张表发生了某件事(插入.删除.更新操作),然后自动触发了预先编写好的若干条SQL语句的执行: 特点及作用 特点:触发事件的操作和触发器里的SQL语句是一个事务操作,具有原子性,要么全部执行,要么都不执行: 作用:保证数据的完整性,起到约束的作用: 例子:创建触发器,记录表的增.删.改操作记录 接下来将创建user和user_history表,以及三个触发器tri_insert_u

oracle(sql)基础篇系列(五)——PLSQL、游标、存储过程、触发器

  PL/SQL PL/SQL 简介 每一种数据库都有这样的一种语言,PL/SQL 是在Oracle里面的一种编程语言,在Oracle内部使用的编程语言.我们知道SQL语言是没有分支和循环的,而PL语言是为了补充SQL语言的,是带有了分支和循环的语言. PL/SQL 语法 基本数据类型声明 declare v_name varchar2(20); v_temp number(1); v_count binary_integer := 0; v_sal number(7,2) := 4000.00

information_schema系列五(表,触发器,视图,存储过程和函数)

这个系列的文章主要是为了能够让自己了解MySQL5.7的一些系统表,统一做一下备注和使用,也希望分享出来让大家能够有一点点的受益. 1:TABLES TABLES这张表毫无疑问了,就是记录的数据库中表的信息,其中包括系统数据库和用户创建的数据库.而TABLESPACES 却是标注的活跃表空间. 看一下TABLES 的记录关于TEST1表: 其实我们通过show table status like 'test1'\G;可以看到相同的信息. TABLE_TYPE这列有两种类型,分别是BASE TAB

触发器和存储过程

触发器和存储过程   1.触发器   在进行dml操作的时候(insert,update,delete),可以对事件进行监听和响应,这种机制在数据库中叫做触发器.   触发器与永久性表关联,只能建在永久性表上面,不能是temporary表或view,且只有insert,update,delete三种事件.   mysql -u root -paixocm   show triggers;   show triggers\G;mysql> delimiter // ====改结束符   自动在内存

oracle 触发器

触发器--启动触发器alter trigger schema_trigger enable;--禁用触发器alter trigger schema_trigger disable;--删除触发器DROP TRIGGER COMPONT_GIS_TEM; 这个触发器是为了增加部件时,同步更新部件的缓存表,缓存表是为了在拿gis数据时,不用再联表查询街道.社区.大类的名称了缓存表中的sequence是  COMPONT_GIS_TEM_SEQ加入触发器的语句是CREATE OR REPLACE TR

mysql触发器

这两天写项目,有个要求是记录用户登录登出操作,把这些操作写到日志表里去,我觉得这个功能用MySQL的触发器来写,更方便一些,所以在这整理了一下这一过程. 在这里,我就不把项目的所以代码都粘贴过来了,项目用的是hibernate,这里我创建两个简单的表来演示一下这个过程. 1:登录表,记录用户的账号,密码以及在线状态,0表示不在线,1表示在线.对status进行默认约束,默认值就是0. create table login( id int not null primary key auto_inc

触发器使用教程和命名规范

目  录 触发器使用教程和命名规范 1 1,触发器简介 1 2,触发器示例 2 3,触发器语法和功能 3 4,例一:行级触发器之一 4 5,例二:行级触发器之二 4 6,例三:INSTEAD OF触发器 6 7,例四:语句级触发器之一 8 8,例五:语句级触发器之二 9 9,例六:用包封装触发器代码 10 10,触发器命名规范 11 1,触发器简介 触发器(Trigger)是数据库对象的一种,编码方式类似存储过程,与某张表(Table)相关联,当有DML语句对表进行操作时,可以引起触发器的执 行

20151016触发器

--触发器:一种特殊的存储过程,通过对数据库表操作的动作,来触发,增删改select *from studentselect *from scoreselect *From fruit drop trigger shanchu--第一种触发器 当某个动作之后触发alter trigger xiugaion Fruitafter update --修改之后的动作as select *From inserted --inserted 表示变动的那一行属性go update Fruit set Num

SQLServer与Oracle的数据同步(触发器trigger)

说到同步,其实是靠"作业"定时调度存储过程来操作数据,增,删,改,全在里面,结合触发器,游标来实现,关于作业调度,使用了5秒运行一次来实行"秒级作业",这样基本就算比较快的"同步" 做的是SQL Server往Oracle端同步,先在sql server上建立往Oracle端的链接服务器,我用一个视图"封装"了一下链接服务器下的一张表. create view v_ora_PUBLISHLASTREC as select *