zoukankan      html  css  js  c++  java
  • (2.5)DDL增强功能-触发器trigger

     嵌套触发器 更详细参考:https://www.cnblogs.com/gered/p/10812399.html

    Sql server触发器详解

    1. 概述

    触发器是一种特殊的存储过程,它不能被显式地调用,而是在往表中插入记录﹑更新记录或者删除记录时被自动地激活。 所以触发器可以用来实现对表实施复杂的完整性约束。

    2. 触发器的分类

    SQL Server2000提供了两种触发器:“Instead of” 和“After” 触发器。

    一个表或视图的每一个修改动作(Insert、Update和Delete)都可以有一个“Instead of” 触发器,一个表的每个修改动作都可以有多个“After”触发器。

    2.1 “Instead of”触发器

    • “Instead of”触发器在执行真正“插入”之前被执行。除表之外,“Instead of” 触发器也可以用于视图,用来扩展视图可以支持的更新操作。
    • “Instead of”触发器会替代所要执行的SQL语句,言下之意就是所要执行SQL并不会“真正执行”
    1
    2
    3
    4
    5
    6
    7
    8
    9
    alter trigger trigger_学生_Delete
    on 学生
    instead of Delete
    as
    begin
        select 学号, 姓名 from deleted
    end
     
    delete from 学生 where 学号 = 4

    上例中定义了“trigger学生_Delete”触发器,该触发器从“delete”表中打印出所要删除的学生.在执行“delete”操作后,会发现“学号 = 4”的学生并未被删除, 原因在于“trigger学生Delete”替代了所要执行的“delete from 学生 where 学号 = 4”语句,而在“trigger学生_Delete”中并未真正删除学生。

    2.2 “After”触发器

    • “After”触发器在Insert、Update或Deleted语句执行之后被触发。“After”触发器只能用于表。
    • “After”触发器主要用于表在修改后(insert、update或delete操作之后),来修改其他表

    3. Inserted和Deleted表

    SQL Server为每个触发器都创建了两个专用表:Inserted表和Deleted表。

    • 这两个表由系统来维护,它们存在于内存中而不是在数据库中,可以理解为一个虚拟的表。
    • 这两个表的结构总是与被该触发器作用的表的结构相同。
    • 触发器执行完成后,与该触发器相关的这两个表也被删除。
    • Deleted表存放由于执行Delete或Update语句而要从表中删除的所有行。
    • Inserted表存放由于执行Insert或Update语句而要向表中插入的所有行。
    对表的操作Inserted逻辑表Deleted逻辑表
    增加记录(insert) 存放增加的记录
    删除记录(delete) 存放被删除的记录
    修改记录(update) 存放更新后的记录 存放更新前的记录

    4. 触发器的执行过程

    • 如果一个Insert﹑update或者delete语句违反了约束,那么这条SQL语句就没有执行成功,因此“After”触发器也不会被激活。

    • “Instead of” 触发器可以取代激发它的操作来执行。它在Inserted表和Deleted表刚刚建立,其它任何操作还没有发生时被执行。因为“Instead of” 触发器在约束之前执行,所以它可以对约束进行一些预处理。

    5. 创建触发器

    1
    2
    3
    4
    create trigger trigger_name
    on  {table_name|view_name}
    {After|Instead of} {insert|update|delete}
    as 相应T-SQL语句

    6. 修改触发器

    alter trigger trigger_name 
    on  {table_name|view_name} 
    {After|Instead of} {insert|update|delete}
    as 相应T-SQL语句

    7. 删除触发器

    1
    drop trigger trigger_name

    8. 查看数据库中已有触发器

    8.1 查看数据库中所有触发器

    1
    select * from sysobjects where xtype='TR'

    8.2 查看单个触发器

    1
    exec sp_helptext '触发器名'

    9. “Instead of”相关示例

    两张表:学生(学号 int, 姓名 varchar)、借书记录(学号 int, 图书编号 int)

    实现功能:在删除学生表时,如果该学生仍有借书记录(未还)则不能删除

    1
    2
    3
    4
    5
    6
    7
    8
    alter trigger trigger_学生_Delete
    on 学生
    instead of Delete
    as
    begin
        if not exists(select * from 借书记录, deleted where 借书记录.学号 = deleted.学号)
            delete from 学生 where 学生.学号 in (select 学号 from deleted)
    end

    10. “After”触发器

    10.1 在“订单”表中建立触发器,当向“订单”表中插入一条订单记录时,检查“商品”表的货品状态“状态”是否为1(正在整理),则不能往“订单”表加入该订单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    create trigger trigger_订单_insert
    on 订单
    after insert
    as
        if (select 状态 from 商品, inserted where 商品.pid = inserted.pid)=1
        begin
            print 'the goods is being processed'
            print 'the order cannot be committed'
            rollback transaction --回滚,避免加入
        end
    • 该示例中“pid”为商品编码
    • 该示例的if判断严格来讲是不准确的,因为“订单”表如果每次插入一条记录,该判断没有问题;如果一次插入多条记录,则“select 状态”返回的是多行。

    10.2 在“订单”表建立一个插入触发器,在添加一条订单时,减少“商品”表相应的货品记录中的库存。

    1
    2
    3
    4
    5
    6
    7
    create trigger trigger_订单_insert2
    on 订单
    after insert
    as
        update 商品 set 数量 = 数量 - inserted.数量
        from 商品, inserted
        where 商品.pid = inserted.pid

    10.3 在“商品”表建立删除触发器,实现“商品”表和“订单”表的级联删除。

    1
    2
    3
    4
    5
    create trigger goodsdelete trigger_商品_delete
    on 商品
    after delete
    as
        delete from 订单 where 订单.pid in (select pid from deleted)

    10.4 在“订单”表建立一个更新触发器,监视“订单”表的“订单日期”列,使其不能被“update”.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    create trigger trigger_订单_update
    on 订单
    after update
    as
        if update(订单日期)
        begin
            raiserror('订单日期不能手动修改',10,1)
            rollback transaction
        end

    10.5 在“订单”表建立一个插入触发器,保证向“订单”表插入的货品必须要在“商品”表中一定存在。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    create trigger trigger_订单_insert3
    on 订单
    after insert
    as
        if (select count(*) from 商品, inserted where 商品.pid = inserted.pid)=0
        begin
            print '商品不存在'
            rollback transaction
        end

    10.6 “订单”表建立一个插入触发器,保证向“订单”表插入的货品信息要在“订单日志”表中添加

    1
    2
    3
    4
    5
    alter trigger trigger_订单_insert
    on 订单
    for insert
    as
        insert into 订单日志 select inserted.Id, inserted.pid,inserted.数量 from inserted

    11. 参考资源

    【11.1】instead of 触发器

    转自:http://www.cnblogs.com/rainman/p/3675834.html#m0

     【11.2】如何判断表是更新、删除、插入?

    所以判断是否insert可以这样子:
    
    if exists(select * from INSERTED)--如果INSERTED表里面存在数据,那么就是insert了
    begin
    ......
    end
    --或者
    if not exists(select * from DELETED)--如果DELETED表里面不存在数据
    begin
    ......
    end 
    判断删除:
    
    if exists(select * from DELETED)
    begin
    --或者
    if not exists(select * from INSERTED)
    begin
    ......
    end 
    判断更新,更新的时候,两个表里面都有数据,所以:
    if(select count(*) from DELETED)>0 and (select count(*) from INSERTED)>0--两个表的数据数量都大于0
    begin
    ......
    end
    --或者
    if exists(select * from INSERTED) and  exists(select * from DELETED
    begin 
    ......
    上面的 这个更新跟当指定的表一有更新就触发,也可以指定某个字段更新时触发,如:
    if update(field)--必须加括号,否则会报错
    begin
    ......

    12.SQL Server 触发器自助抛异常并回滚事务

    CREATE TRIGGER [dbo].[tri_after_modify]
    ON [dbo].[tablename]
    AFTER INSERT, UPDATE, DELETE
    AS
    if(select COUNT(*) from deleted) > 1
    begin
        rollback
        raiserror ('错误信息:一次只能修改一个。',17,1)
        return
    end

    13.触发器中的坑,一次性插入/更新/删除多条数据只会触发一次触发器

    比如 insert into table values(1,1),(2,2),这样只会触发一次触发器。

    但inserted 中会有2行数据。所以需要遍历inserted,不管是用游标还是构建临时表,均可

    【13.1】最佳实践

    alter trigger trg_test 
    on  KR_Bank_Back
    After insert,update
    as 
    begin
        declare @Issuccess varchar(1000),@win_cost_get decimal(15,2),@userName varchar(1000),@str varchar(100)
        declare @rn int,@rn_count int
        select *,row_number() over(order by Issuccess) rn into #temp1 from INSERTED
        select @rn=1,@rn_count=count(1) from #temp1
        set @str='已中奖'
        while @rn<=@rn_count
        begin
        
        
            select @Issuccess = Issuccess,@win_cost_get=win_cost_get,@userName=userName from  #temp1 where rn=@rn
            IF CHARINDEX(@str,@Issuccess)>0
            begin
                update KR_User set Record=Record+@win_cost_get where userName=@userName
            end
            set @rn=@rn+1
        end
    
    end

    14.我的最佳实践案例

    需求:update触发器,当我更新了表2的审核标志的时候,触发更新表1的相关字段数据。

    要求:

      (1)审核标志字段 ISGIVE 更新值只能为0、1,否则应该抛出错误

      (2)如果表行更新数据没有更新到ISGIVE字段,则不触发 触发器。

    业务修改分两种情况

      (1)表2审核标志更新为1的时候,触发把表2的审核标志和审核时间值写到表1即可;

      (2)审核标志更新为0的时候,除了把表2的审核标志和审核时间值写到表1还需要将表1的申请状态改为0;

    【14.1】测试数据

    use test;
    go
    
    create table BLSJ_PRESCRIPTION_INFO(
    PRESCRIPTION_ID     int, --处方ID
    PATIENT_ID    int,--患者ID
    PRESCRIPTION_TIME datetime,    --处方时间
    PRESCRIPTION_AMOUNT    int,--处方数量
    ISGIVE char(2),--审核标志
    GIVETIMEID    datetime,--审核时间
    APPLYTIMEID    datetime ,--申请时间
    ISAPPLY char(2)--申请状态
    );
    
    
    create table BLSJ_SCORE_DETAIL2(
    SCORE_DETAIL_ID int, --积分申请ID
    DOCTOR_ID int,--医生ID
    CF_AMOUNT int,--处方数量
    SCORE int,--给予积分
    CREATE_TIME    datetime,--申请时间
    ISAPPLY    char(2),--申请状态
    ISGIVE char(2),--审核标志
    GIVETIMEID    datetime,--审核时间
    MEMO    char(10),--审核意见
    apply_sc    int,--申请数量
    apply_memo varchar(200)--申请备注
    );
    
                        
    insert into BLSJ_PRESCRIPTION_INFO values(2138,3078,'2020-3-1 14:16',35,'',null,'2020-3-9 14:16','1')
    insert into BLSJ_PRESCRIPTION_INFO values(2140,3078,'2020-3-1 14:16',25,'',null,'2020-3-9 14:16','1')                
    insert into BLSJ_SCORE_DETAIL2 values(2023,2085,60,300,'2020-3-9 14:16','1','',null,'',0,'')
    
    select * from BLSJ_PRESCRIPTION_INFO
    select *,row_number() over(order by SCORE_DETAIL_ID) as rn from BLSJ_SCORE_DETAIL2
    
    --delete   BLSJ_PRESCRIPTION_INFO
    --delete   BLSJ_SCORE_DETAIL2
    --update BLSJ_SCORE_DETAIL2 set apply_memo='更新ISGIVE为非0/1字段测试',ISGIVE=3

    【14.2】update 触发器(建议insert和delete 使用临时表的方式)

    use test
    go
    if object_id('try_test') is not null
        drop trigger try_test
    go
    
    create trigger try_test
    on BLSJ_SCORE_DETAIL2
    after update
    as 
    begin
        declare @rn int,@rn_count int
        declare @ISGIVE varchar(10)
        select *,row_number() over(order by SCORE_DETAIL_ID) as rn into #temp1 from inserted
        set @rn=1
        select @rn_count=count(*) from #temp1
    
        IF update(ISGIVE) --判断字段是否有更新
        begin
            while @rn<=@rn_count
            begin
                select @ISGIVE=ISGIVE from #temp1 where rn=@rn
                if @ISGIVE='1'
                    update t1
                    set ISGIVE=t2.ISGIVE,GIVETIMEID=t2.GIVETIMEID
                    from BLSJ_PRESCRIPTION_INFO t1
                    join #temp1 t2 
                    on t2.CREATE_TIME=t1.APPLYTIMEID
                    and t2.rn=@rn
                else
                    if @ISGIVE='0'
                        update t1
                        set ISGIVE=t2.ISGIVE,GIVETIMEID=t2.GIVETIMEID,ISAPPLY=0
                        from BLSJ_PRESCRIPTION_INFO t1
                        join #temp1 t2 
                        on t2.CREATE_TIME=t1.APPLYTIMEID
                        and t2.rn=@rn
                    else
                    begin
                        raiserror ('触发器:try_test,修改数据错误,表:BLSJ_SCORE_DETAIL2的修改行中ISGIVE字段不为0也不为1。改更新事务已回滚',17,1)
                        rollback transaction
                    end
                set @rn=@rn+1
            end
        end    
    
    end

    【14.2】简化(如果条件统一,如本需求,建议使用这种办法)

    use test
    go
    if object_id('try_test') is not null
        drop trigger try_test
    go
    
    create trigger try_test
    on BLSJ_SCORE_DETAIL2
    after update
    as 
    begin
        declare @ISGIVE varchar(10)
        IF update(ISGIVE)    
        begin
            
                select @ISGIVE=ISGIVE from inserted
                if @ISGIVE='1'
                    update t1
                    set ISGIVE=t2.ISGIVE,GIVETIMEID=t2.GIVETIMEID
                    from BLSJ_PRESCRIPTION_INFO t1
                    join inserted t2 
                    on t2.CREATE_TIME=t1.APPLYTIMEID
                    
                else
                    if @ISGIVE='0'
                        update t1
                        set ISGIVE=t2.ISGIVE,GIVETIMEID=t2.GIVETIMEID,ISAPPLY=0
                        from BLSJ_PRESCRIPTION_INFO t1
                        join inserted t2 
                        on t2.CREATE_TIME=t1.APPLYTIMEID
                    else
                    begin
                        raiserror ('触发器:try_test,修改数据错误,表:BLSJ_SCORE_DETAIL2的修改行中ISGIVE字段不为0也不为1。改更新事务已回滚',17,1)
                        rollback transaction
                    end    
            
        end    
    
    end

    【14.3】测试验证

     1.原始数据 
      
     2.不更新isgive字段测试 
      
     3.更新ISGIVE为非0/1字段测试 
      

     4.核心,更新ISGIVE为1字段测试

      

     5.核心,更新ISGIVE为0字段测试 
      
  • 相关阅读:
    Java+7入门经典 -1 简介
    优化算法动画演示Alec Radford's animations for optimization algorithms
    如何写科技论文How to write a technical paper
    开始学习深度学习和循环神经网络Some starting points for deep learning and RNNs
    用500行Julia代码开始深度学习之旅 Beginning deep learning with 500 lines of Julia
    用10张图来看机器学习Machine learning in 10 pictures
    ICLR 2013 International Conference on Learning Representations深度学习论文papers
    ICLR 2014 International Conference on Learning Representations深度学习论文papers
    卷积神经网络CNN(Convolutional Neural Networks)没有原理只有实现
    卷积神经网络Convolutional Neural Networks
  • 原文地址:https://www.cnblogs.com/gered/p/9132733.html
Copyright © 2011-2022 走看看