13.1 SQL Server触发器
SQL Server 触发器(Triggers)
简介
SQLServer触发器是响应数据库对象、数据库和服务器事件自动执行的特殊存储过程。SQL Server提供了三种类型的触发器:
- 数据操作语言(DML)触发器,用于对表的
INSERT
、UPDATE
和DELETE
事件时而自动调用。 - 数据定义语言(DDL)触发器,用于响应
CREATE
、ALTER
和DROP
语句。DDL触发器也会响应某些执行类似DDL操作的系统存储过程而触发。 - 响应Logon事件触发的登录触发器
创建AFTER触发器(DML)
简介
AFTER 触发器(事后触发器)
AFTER
触发器定义了对表执行了 INSERT
、UPDATE
或 DELETE
语句操作之后再执行的操作。比如对某个表中的数据进行了更新操作后,要求立即对相关的表进行指定的操作,这时就可以采用 AFTER
触发器。AFTER
触发器只能在表上指定,且动作晚于约束处理。
每一个表上只能创建一个 INSTEAD OF
触发器,但可以创建多个 AFTER
触发器。
SQL Server CREATE TRIGGER
语句
CREATE TRIGGER
语句创建一个新触发器,当表发生INSERT
、DELETE
或UPDATE
等事件时,该触发器将自动触发。
语法:
CREATE TRIGGER [schema_name.]trigger_name
ON table_name
AFTER {[INSERT],[UPDATE],[DELETE]}
[NOT FOR REPLICATION]
AS
{sql_statements}
其中:
schema_name
是新触发器所属的架构的名称。架构名称是可选的。trigger_name
是新触发器的名称。table_name
是应用触发器的表。- 事件列在
AFTER
子句中。事件可以是INSERT
、UPDATE
或DELETE
。单个触发器可以触发对表的一个或多个操作。 NOT FOR REPLICATION
选项指示SQL Server在作为复制过程的一部分进行数据修改时不触发触发器。sql_statements
是一个或多个T-sql,用于在事件发生后执行操作。
触发器的“虚拟”表:INSERTED和DELETED
SQLServer为触发器提供了两个虚拟表,名为INSERTED
和DELETED
表。SQLServer使用这两个表来捕获事件发生前后修改行的数据。
下表显示了每个事件前后INSERTED
和DELETED
表的内容:
操作 | INSERTED表内容 | INSERTED表内容 |
---|---|---|
INSERT | 要插入的行 | 空 |
UPDATE | 更新修改的新行 | 被更新修改的现有行: |
DELETE | 空 | 要删除的行 |
创建触发器示例
使用如下产品表用于示例:
1)创建用于记录更改的审计表
先创建了一个名为production.product_audits
的表。用于在production.products
表发生INSERT
或DELETE
事件时记录信息:
CREATE TABLE production.product_audits(
change_id INT IDENTITY PRIMARY KEY, --主键
product_id INT NOT NULL, --产品Id
product_name VARCHAR(255) NOT NULL, --产品名
brand_id INT NOT NULL, --品牌Id
category_id INT NOT NULL, --分类Id
model_year SMALLINT NOT NULL, --那一年的产品
list_price DEC(10,2) NOT NULL, --标价
updated_at DATETIME NOT NULL, --更新时间
operation CHAR(3) NOT NULL, --操作方式
CHECK(operation = 'INS' or operation='DEL')
);
production.product_audits
表包含production.products
(产品)表的所有列,此外,还有一些列来记录更改,例如updated_at
、operation
和change_id
。
注意,对于重要的数据表,最好是做这样一个审计表来记录对数据的修改,最好再添加上修改人。
2)创建一个AFTER
DML触发器
首先,要创建新触发器,请在CREATE TRIGGER
子句中指定触发器的名称和触发器所属的模式:
CREATE TRIGGER production.trg_product_audit
然后再指定触发器对应的表:
ON production.products
然后,在AFTER
子句中列出将触发触发器
的一个或多个事件:
AFTER INSERT, DELETE
触发器的主体逻辑代码以AS
关键字开头:
AS
BEGIN
然后,在触发器的主体中,将SET NOCOUNT
设置为ON
,以禁止在触发触发器时返回受影响的行数。
SET NOCOUNT ON;
每当一行插入production.products
表或从production.products
表中删除时,触发器将在production.product_audits
表中插入一行。插入的数据通过UNION ALL
运算符从INSERTED
和DELETED
表中提供:
完整代码:
CREATE TRIGGER production.trg_product_audit
ON production.products
AFTER INSERT, DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO production.product_audits(
product_id,
product_name,
brand_id,
category_id,
model_year,
list_price,
updated_at,
operation
)
SELECT
i.product_id,
product_name,
brand_id,
category_id,
model_year,
i.list_price,
GETDATE(),
'INS'
FROM
inserted i
UNION ALL
SELECT
d.product_id,
product_name,
brand_id,
category_id,
model_year,
d.list_price,
GETDATE(),
'DEL'
FROM
deleted d;
END
最后,执行整个语句来创建触发器。创建触发器后,您可以在表的触发器文件夹下找到它,如下图所示:
3)测试触发器
咱们对表production.products
插入一个新行:
INSERT INTO production.products(
product_name,
brand_id,
category_id,
model_year,
list_price
)
VALUES (
'Test product',
1,
1,
2018,
599
);
由于是INSERT
事件,前面创建的触发器被触发。
我们来检查一下production.product_audits
表
SELECT
*
FROM
production.product_audits;
没有问题,确实有一条插入记录
然后我们来从production.products
表删除一行:
DELETE FROM
production.products
WHERE
product_id = 322;
触发器应该也被触发
检查一下审计表production.product_audits
:
SELECT
*
FROM
production.product_audits;
果然有一条删除记录,没有任何问题。
创建INSTEAD OF触发器(DML)
简介
INSTEAD OF
触发器(替代触发器)
INSTEAD OF
触发器用来代替通常的触发动作,即当对表进行INSERT
、UPDATE
或 DELETE
操作时,系统不是直接对表执行这些操作,而是把操作内容交给触发器,让触发器检查所进行的操作是否正确。如正确才进行相应的操作。因此,INSTEAD OF
触发器的动作要早于表的约束处理。
INSTEAD OF
触发器的操作有点类似于完整性约束。在对数据库的操纵时,有些情况下使用约束可以达到更好的效果,而如果采用触发器,则能定义比完整性约束更加复杂的约束。
INSTEAD OF
触发器不仅可在表上定义,还可在带有一个或多个基表的视图上定义,但在作为级联引用完整性约束目标的表上限制应用。
创建语法
CREATE TRIGGER [schema_name.] trigger_name
ON {table_name | view_name }
INSTEAD OF {[INSERT] [,] [UPDATE] [,] [DELETE] }
AS
{sql_statements}
其中:
- 首先
schema_name
是新触发器所属的架构的名称。架构名称是可选的。trigger_name
是新触发器的名称。 - 其次,指定触发器关联的表(
table_name
)或视图(view_name
)的名称。 - 第三,指定触发器将在
INSTEAD OF
子句中触发的事件,如INSERT
、DELETE
或UPDATE
。可以响应一个或多个事件。 - 第四,将触发器主体放在
AS
关键字之后。触发器的主体可以由一个或多个T-SQL语句组成。
示例
使用INSTEAD OF
触发器的典型示例是覆盖视图上的插入、更新或删除操作。
假设一个应用程序需要在品牌表(production.brands
)中插入一个新品牌。
然而,新品牌应该存储在另一个称为production.brand_approvals
(品牌审核)的表中。production.brand_approvals
表用于在插入production.brands
表之前获得批准审核。
production.brand_approvals
表中的品牌审核通过了再插入到production.brands
表
要实现这一点,您需要创建一个名为production.vw_brands
的视图。production.vw_brands
用于应用程序插入新品牌。如果将品牌插入视图,将触发INSTEAD OF
触发器来将品牌插入production.brand_approvals
审核表。
过程如下:
先创建production.brand_approvals
审核表用于存储待审批的品牌:
CREATE TABLE production.brand_approvals(
brand_id INT IDENTITY PRIMARY KEY,
brand_name VARCHAR(255) NOT NULL
);
再创建production.vw_brands
品牌视图。对应production.brands
和production.brand_approvals
表:
CREATE VIEW production.vw_brands
AS
SELECT
brand_name,
'Approved' approval_status
FROM
production.brands
UNION
SELECT
brand_name,
'Pending Approval' approval_status
FROM
production.brand_approvals;
一旦一行插入到production.vw_brands
视图,我们通过以下INSTEAD OF
触发器将其路由到production.brand_approvals
表:
CREATE TRIGGER production.trg_vw_brands
ON production.vw_brands
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO production.brand_approvals (
brand_name
)
SELECT
i.brand_name
FROM
inserted i
WHERE
i.brand_name NOT IN (
SELECT
brand_name
FROM
production.brands
);
END
如果production.brands
表中不存在新插入的品牌名称,触发器就将新品牌名称插入production.brand_approvals
表等待审核。
测试一下,我们向production.vw_brands
视图中插入一个新品牌:
INSERT INTO production.vw_brands(brand_name)
VALUES('Eddy Merckx');
此INSERT
语句触发了INSTEAD OF
触发器以将新行插入到production.brand_approvals
表。
此时如果从production.vw_brands
视图中查询数据将看到一个新行:
SELECT
brand_name,
approval_status
FROM
production.vw_brands;
再来看看品牌审核表:
SELECT
*
FROM
production.brand_approvals;
SQL Server DDL触发器
简介
SQLServer DDL触发器响应服务器或数据库事件,而不是表数据修改。这些事件由T-SQL语句创建,通常以以下关键字之一CREATE
、ALTER
、DROP
、GRANT
、DENY
、REVOKE
或UPDATE STATISTICS
开头。
比如,只要用户执行CREATE TABLE
或ALTER TABLE
语句,就可以编写DDL触发器来记录日志。
DDL触发器在以下情况下很有用:
- 记录数据库架构(schema)中的更改。
- 阻止对数据库架构进行某些特定更改。
- 响应数据库架构中的更改。
语法:
CREATE TRIGGER trigger_name
ON { DATABASE | ALL SERVER}
[WITH ddl_trigger_option]
FOR {event_type | event_group }
AS {sql_statement}
trigger_name
触发器名字,注意不用指定架构名,因为DDL触发器与实际的表或者视图无关
DATABASE | ALL SERVER
触发器作用范围,如果触发器响应数据库范围的事件,则使用DATABASE
;如果触发器响应服务器范围的事件则使用ALL SERVER
。
ddl_trigger_option
ddl_trigger_option
指定ENCRYPTION
和/或EXECUTE AS
子句。ENCRYPTION
加密触发器的定义。EXECUTE AS
定义执行触发器的安全上下文。
event_type | event_group
event_type
表示导致触发器触发的DDL事件,例如CREATE_TABLE
、ALTER_TABLE
等。
event_group
是一组event_type
事件,如DDL_TABLE_EVENTS
。
触发器可以订阅一个或多个事件或事件组。
示例
假设我们想捕获对数据库索引所做的所有修改,方便更好地监视与这些索引更改相关的数据库服务器的性能。
首先,创建一个名为index_logs
的新表来记录索引更改:
CREATE TABLE index_logs (
log_id INT IDENTITY PRIMARY KEY,
event_data XML NOT NULL,
changed_by SYSNAME NOT NULL
);
GO
然后,创建一个DDL触发器来跟踪索引更改并将事件数据插入index_logs
表:
CREATE TRIGGER trg_index_changes
ON DATABASE
FOR
CREATE_INDEX,
ALTER_INDEX,
DROP_INDEX
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO index_logs (
event_data,
changed_by
)
VALUES (
EVENTDATA(),
USER
);
END;
GO
在触发器的主体代码中,使用了EVENTDATA()
函数,该函数返回有关服务器或数据库事件的信息。该函数仅在DDL或登录触发器内可用。
然后,为sales.customers
表的first_name
和last_name
列创建索引:
CREATE NONCLUSTERED INDEX nidx_fname
ON sales.customers(first_name);
GO
CREATE NONCLUSTERED INDEX nidx_lname
ON sales.customers(last_name);
GO
然后,查询index_logs
表中的数据,检查触发器是否正确捕获了索引创建事件:
SELECT
*
FROM
index_logs;
如果单击event_data
列的单元格,可以查看事件的详细XML数据:
禁用触发器
简介
有时,出于故障排除或数据恢复的目的,可能需要暂时禁用触发器。为此,可以使用DISABLE TRIGGER
语句:
DISABLE TRIGGER [schema_name.][trigger_name]
ON [object_name | DATABASE | ALL SERVER]
其中:
- 首先,在
DISABLE TRIGGER
之后指定触发器所属架构的名称和要禁用的触发器的名称。 - 其次,如果触发器是DML触发器,则指定触发器绑定到的表名或视图。如果触发器是DDL数据库范围的触发器,则使用
DATABASE
;如果触发器为DDL服务器范围的触发器则使用ALL SERVER
。
禁用单个触发器
创建一个会员表用于演示:
CREATE TABLE sales.members (
member_id INT IDENTITY PRIMARY KEY,
customer_id INT NOT NULL,
member_level CHAR(10) NOT NULL
);
下面创建一个触发器,每当新行插入到sales.members
中时就会触发该触发器。出于演示目的,触发器只返回一条简单的消息。
CREATE TRIGGER sales.trg_members_insert
ON sales.members
AFTER INSERT
AS
BEGIN
PRINT 'A new member has been inserted';
END;
现在尝试在表中插入一行:
INSERT INTO sales.members(customer_id, member_level)
VALUES(1,'Silver');
由于INSERT事件,触发器被触发并打印出以下消息:
A new member has been inserted
现在使用如下语句关闭该触发器:
DISABLE TRIGGER sales.trg_members_insert
ON sales.members;
现在再在会员表插入新行,将不会触发触发器:
INSERT INTO sales.members(customer_id, member_level)
VALUES(2,'Gold');
但是触发器定义仍然在表中。如果在SQL Server Management Studio(SSMS)中查看触发器,将注意到禁用的触发器名称上有一个红色十字图标:
禁用表中的所有触发器
语法:
DISABLE TRIGGER ALL ON table_name;
只需指定表的名称即可禁用属于该表的所有触发器。
禁用数据库中的所有触发器
DISABLE TRIGGER ALL ON DATABASE;
启用触发器
简介
语法:
ENABLE TRIGGER [schema_name.][trigger_name]
ON [object_name | DATABASE | ALL SERVER]
其中:
- 首先,指定要启用的触发器的名称。也可以指定触发器所属的架构的名称。
- 其次,如果触发器是DML触发器,需指定触发器所属的表。如果触发器是DDL数据库范围的触发器,则使用
DATABASE
;如果触发器是DDL服务器范围的触发器则使用ALL SERVER
。
示例
启用表中单个触发器
比如要启用sales.members
表中的触发器sales.trg_members_insert
:
ENABLE TRIGGER sales.trg_members_insert
ON sales.members;
启用后,可以通过SQL Server Management Studio查看触发器的状态,如下图所示:
启用表中所有触发器
ENABLE TRIGGER ALL ON table_name;
只需要指定表名就可以了
比如要启用sales.members
表所有触发器:
ENABLE TRIGGER ALL ON sales.members;
下图为sales.members
的所有触发器的状态:
启用数据库的所有触发器
ENABLE TRIGGER ALL ON DATABASE;
查看触发器的定义
通过查询系统视图
可以通过sys.sql_modules
视图查看触发器定义:
SELECT
definition
FROM
sys.sql_modules
WHERE
object_id = OBJECT_ID('sales.trg_members_delete');
在这个查询中,将要获取定义的触发器的名称传递给WHERE
子句中的OBJECT_ID()
函数。
使用OBJECT_definition函数
SELECT
OBJECT_DEFINITION (
OBJECT_ID(
'sales.trg_members_delete'
)
) AS trigger_definition;
在这个查询中,将触发器名称传递给OBJECT_ID
函数以获取触发器的ID
。然后,使用OBJECT_DEFINITION()
函数根据触发器的ID获取触发器定义的T-SQL源代码。
使用sp_helptext存储过程
EXEC sp_helptext 'sales.trg_members_delete' ;
sp_helptext
存储过程返回已创建对象(在本例中为触发器)的定义。
通过SSMS
要查看DML触发器的定义:
在如下图中表的触发器目录下,右击对应触发器,然后点击Modify(修改),即可查看触发器定义:
列出所有触发器
要列出SQL Server中的所有触发器,可以从sys.triggers
视图中查询数据:
SELECT
name,
is_instead_of_trigger
FROM
sys.triggers
WHERE
type = 'TR';
输出:
删除触发器
简介
SQL Server DROP TRIGGER
语句从数据库中删除一个或多个触发器。以下是删除DML触发器的DROP TRIGGER
语句的语法:
DROP TRIGGER [ IF EXISTS ] [schema_name.]trigger_name [ ,...n ];
如果要同时删除多个触发器,则需要用逗号分隔。
要删除一个或多个DDL触发器,请使用以下形式的DROP TRIGGER
语句:
DROP TRIGGER [ IF EXISTS ] trigger_name [ ,...n ]
ON { DATABASE | ALL SERVER };
DATABASE
表示DDL触发器的作用域应用于当前数据库。ALL SERVER
表示应用于当前服务器的DDL触发器的范围。
要删除LOGON事件触发器,使用以下语法:
DROP TRIGGER [ IF EXISTS ] trigger_name [ ,...n ]
ON ALL SERVER;
注意,当删除一个表时,与该表关联的所有触发器也会自动删除。
示例
A)删除一个DML触发器
删除名为sales.trg_members_insert
触发器:
DROP TRIGGER IF EXISTS sales.trg_member_insert;
B)删除一个DDL触发器
删除trg_index_changes
触发器:
DROP TRIGGER IF EXISTS trg_index_changes;