记首次尝试SQL Server触发器

简述

由于业务需求,常常需要在数据库中某些表发生变化时,进行一些操作,如记录日志以备份或后期进行数据追溯、同步不同数据库表的某些状态和字段等。在拿到这些需求时,往往也会第一时间考虑通过书写逻辑层业务代码进行。这种做法比较通用,但往往效率较低,并且不便于维护和更新,因此考虑使用数据库的触发器。本文记录了第一次尝试书写SQL Server的触发器的经验。

初始化

为了测试,首先建立一个用于存储数据的测试表(TriggerTestTable),以及一个用于测试触发器的日志表(LogTable),结构和数据如下:

CREATE DATABASE mydb
USE mydb
GO

CREATE TABLE [dbo].[TriggerTestTable] (
    [id] int NOT NULL ,
    [Val] varchar(20) NULL ,
    [OtherVal] int NULL ,
    PRIMARY KEY ([id])
)
GO

CREATE TABLE [dbo].[LogTable] (
    [id] varchar(30) NOT NULL ,
    [ChangedTableName] varchar(255) NULL ,
    [ChangeLog] varchar(255) NULL ,
    PRIMARY KEY ([id])
)
GO

--------------- 添加初始数据 ---------------
INSERT INTO TriggerTestTable(id, Val, OtherVal) VALUES (1, '1', 11);
INSERT INTO TriggerTestTable(id, Val, OtherVal) VALUES (2, '2', 12);
INSERT INTO TriggerTestTable(id, Val, OtherVal) VALUES (3, '3', 13);
INSERT INTO TriggerTestTable(id, Val, OtherVal) VALUES (4, '4', 14);
INSERT INTO TriggerTestTable(id, Val, OtherVal) VALUES (5, '5', 15);
GO

初始化后LogTable没有数据,TriggerTestTable数据如下:

idValOtherVal
1111
2212
3313
4414
5515

触发器书写

参考网上的一篇博客,浅谈 Sql Server 触发器,尝试进行触发器的书写。发现触发器本质上其实就是一种存储过程,涉及到“增、删、改”操作中的临时表“inserted”和“deleted”和与业务相关的表。

在阅读该博客中的“update 触发器”部分时突然想到,如果Update了数条数据,是否能够保证每条数据都能经过触发器。于是简单尝试了如下触发器,目的在Update测试表TriggerTestTable中Val的时候将修改前后的信息记录进日志表LogTable中。

--------------- 为TriggerTestTable表创建Update触发器 ---------------
CREATE TRIGGER [dbo].[trig_updateValue1]
ON [dbo].[TriggerTestTable]
AFTER UPDATE
AS
BEGIN
    DECLARE @oldVal varchar(20),
            @newVal varchar(20),
            @id INT;
    
    SELECT @oldVal = Val, @id = id FROM deleted;
    SELECT @newVal = Val FROM inserted;

    INSERT INTO LogTable(id, ChangedTableName, ChangeLog) 
    VALUES (
    	convert(VARCHAR, @id),
    	'TriggerTestTable',
    	'OldValue: ' + @oldVal + '; NewValue: ' + @newVal
    );
END
GO

--------------- 执行批量修改语句 ---------------
UPDATE TriggerTestTable
SET Val = '99'
WHERE id > 2;
GO

在最后执行批量更新语句后,查看TriggerTestTable表发现更新语句成功执行,id大于2的Val均修改为99:

idValOtherVal
1111
2212
39913
49914
59915

但LogTable中仅有一条记录:

idChangedTableNameChangeLog
3TriggerTestTableOldValue: 3; NewValue: 99

由此推断,批量更新的语句仅仅触发了一次触发器。批量更新时原记录全部加入到“deleted”临时表中,新记录全部加入到“inserted”临时表中,但最终触发器中的语句却仅仅执行了一次。为了验证临时表中的数据量,又书写了如下触发器:

--------------- 禁用旧的TriggerTestTable表的触发器 ---------------
ALTER TABLE [dbo].[TriggerTestTable] 
DISABLE TRIGGER [dbo].[trig_updateValue1]
GO

--------------- 为TriggerTestTable表创建Update触发器 ---------------
CREATE TRIGGER [dbo].[trig_updateValue2]
ON [dbo].[TriggerTestTable]
AFTER UPDATE
AS
BEGIN
    DECLARE @oldCount INT,
            @newCount INT,
            @id INT;
    
    SELECT @oldCount = count(*) FROM deleted;
    SELECT @newCount = count(*) FROM inserted;
    
    INSERT INTO LogTable(id, ChangedTableName, ChangeLog) 
    VALUES (
    	'99', 
    	'TriggerTestTable',
    	'DeleteCount: ' + convert(VARCHAR, @oldCount) + '; InsertCount: ' + convert(VARCHAR, @newCount)
    );
END
GO

--------------- 执行批量修改语句 ---------------
UPDATE TriggerTestTable
SET Val = '88'
WHERE id > 2;
GO

执行批量更新语句后,TriggerTestTable表中id大于2的Val均修改为88:

idValOtherVal
1111
2212
38813
48814
58815

LogTable中的记录也表明了“inserted”和“deleted”临时表中的数据确实均有3条:

idChangedTableNameChangeLog
3TriggerTestTableOldValue: 3; NewValue: 99
99TriggerTestTableDeleteCount: 3; InsertCount: 3

批量Update触发器

这时就考虑到两种解决方案:

  1. 保证增、删、改每次只影响到一条数据;
  2. 在触发器中通过游标遍历临时表对每条修改数据进行操作。

其中第一种方案显然很不通用,于是便还是尝试使用游标遍历临时表,对每一条修改的数据进行对应的操作。由于本人目前对存储过程还不是很了解,故仍然参考博客SqlServer使用For循环结合游标更新数据熟悉了一下SQL Server中的游标。修改Update触发器如下:

--------------- 禁用旧的TriggerTestTable表的触发器 ---------------
ALTER TABLE [dbo].[TriggerTestTable] 
DISABLE TRIGGER [dbo].[trig_updateValue1]

ALTER TABLE [dbo].[TriggerTestTable] 
DISABLE TRIGGER [dbo].[trig_updateValue2]
GO

--------------- 为TriggerTestTable表创建Update触发器 ---------------
CREATE TRIGGER [dbo].[trig_updateValue3]
ON [dbo].[TriggerTestTable]
AFTER UPDATE
AS
BEGIN
    IF update(Val)  -- 仅在Val列变化时才执行
    BEGIN
        DECLARE @oldVal varchar(20),
                @newVal varchar(20),
                @id INT;

        DECLARE ID_Cursor CURSOR --定义游标
        FOR (SELECT id FROM deleted) --查出需要的集合放到游标中

        OPEN ID_Cursor; --打开游标
        FETCH NEXT FROM ID_Cursor INTO @id;
        WHILE @@FETCH_STATUS = 0  --判断游标是否为读取失败,读取失败则为-1,代表到达最后一行
            BEGIN
                SELECT @oldVal = Val FROM deleted WHERE id = @id;
                SELECT @newVal = Val FROM inserted WHERE id = @id;

                INSERT INTO sync_log(id,tableName,cols) 
                VALUES (
                	convert(VARCHAR, @id + 100),
                	'TriggerTestTable',
                	'OldValue: ' + @oldVal + '; NewValue: ' + @newVal
                );

                FETCH NEXT FROM ID_Cursor INTO @id;
            END
        CLOSE ID_Cursor; --关闭游标
        DEALLOCATE ID_Cursor; --释放游标
    END
END
GO

--------------- 执行批量修改语句 ---------------
UPDATE TriggerTestTable
SET Val = '66'
WHERE id > 2;
GO

批量更新语句成功执行,TriggerTestTable表中id大于2的Val均修改为66:

idValOtherVal
1111
2212
36613
46614
56615

LogTable中也新增了3条记录,说明触发器成功遍历“inserted”和“deleted”临时表,并进行了对应的操作,每条数据的修改都得到了记录:

idChangedTableNameChangeLog
3TriggerTestTableOldValue: 3; NewValue: 99
99TriggerTestTableDeleteCount: 3; InsertCount: 3
103TriggerTestTableOldValue: 88; NewValue: 66
104TriggerTestTableOldValue: 88; NewValue: 66
105TriggerTestTableOldValue: 88; NewValue: 66

之后为了测试update()函数,又批量修改了OtherVal字段:

--------------- 执行批量修改语句 ---------------
UPDATE TriggerTestTable
SET OtherVal = 99
WHERE id < 3;
GO

批量更新语句成功执行,TriggerTestTable表中id小于3的OtherVal均修改为99:

idValOtherVal
1199
2299
36613
46614
56615

但由于添加了IF update(Val)语句,在修改OtherVal字段时虽然进入了触发器,但Val字段没有变化,故并未执行触发器中的具体操作,所以LogTable并没有任何变化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值