13.1 SQL Server触发器


 

SQL Server 触发器(Triggers)

 

 

简介

SQLServer触发器是响应数据库对象、数据库和服务器事件自动执行的特殊存储过程。SQL Server提供了三种类型的触发器:

  • 数据操作语言(DML)触发器,用于对表的INSERTUPDATEDELETE事件时而自动调用。
  • 数据定义语言(DDL)触发器,用于响应CREATEALTERDROP语句。DDL触发器也会响应某些执行类似DDL操作的系统存储过程而触发。
  • 响应Logon事件触发的登录触发器

创建AFTER触发器(DML)

简介

AFTER 触发器(事后触发器)
AFTER 触发器定义了对表执行了 INSERTUPDATE 或 DELETE 语句操作之后再执行的操作。比如对某个表中的数据进行了更新操作后,要求立即对相关的表进行指定的操作,这时就可以采用 AFTER 触发器。AFTER 触发器只能在表上指定,且动作晚于约束处理。
每一个表上只能创建一个 INSTEAD OF 触发器,但可以创建多个 AFTER 触发器。

SQL Server CREATE TRIGGER语句

CREATE TRIGGER语句创建一个新触发器,当表发生INSERTDELETEUPDATE等事件时,该触发器将自动触发。

语法:

  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子句中。事件可以是INSERTUPDATEDELETE。单个触发器可以触发对表的一个或多个操作。
  • NOT FOR REPLICATION选项指示SQL Server在作为复制过程的一部分进行数据修改时不触发触发器。
  • sql_statements是一个或多个T-sql,用于在事件发生后执行操作。

触发器的“虚拟”表:INSERTED和DELETED

SQLServer为触发器提供了两个虚拟表,名为INSERTEDDELETED表。SQLServer使用这两个表来捕获事件发生前后修改行的数据。

下表显示了每个事件前后INSERTEDDELETED表的内容:

操作INSERTED表内容INSERTED表内容
INSERT 要插入的行
UPDATE 更新修改的新行 被更新修改的现有行:
DELETE 要删除的行

创建触发器示例

使用如下产品表用于示例:

1)创建用于记录更改的审计表

先创建了一个名为production.product_audits的表。用于在production.products表发生INSERTDELETE事件时记录信息:

  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_atoperationchange_id

注意,对于重要的数据表,最好是做这样一个审计表来记录对数据的修改,最好再添加上修改人。

2)创建一个AFTERDML触发器

首先,要创建新触发器,请在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运算符从INSERTEDDELETED表中提供:

完整代码:

  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 触发器用来代替通常的触发动作,即当对表进行INSERTUPDATE 或 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子句中触发的事件,如INSERTDELETEUPDATE。可以响应一个或多个事件。
  • 第四,将触发器主体放在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.brandsproduction.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语句创建,通常以以下关键字之一CREATEALTERDROPGRANTDENYREVOKEUPDATE STATISTICS开头。

比如,只要用户执行CREATE TABLEALTER 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_TABLEALTER_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_namelast_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;
 
分类: SQL Server

posted on 2025-04-22 00:01  漫思  阅读(37)  评论(0)    收藏  举报

导航