【问题标题】:what is the correct syntax for creating a database trigger for insert, modify and delete为插入、修改和删除创建数据库触发器的正确​​语法是什么
【发布时间】:2010-01-06 16:58:42
【问题描述】:

我有一个似乎是 SQL 服务器中 db 触发器的基本场景,但我遇到了一个问题。

我有表 Users(id、姓名、电话等),我有表 UsersHistory(id、user_id 操作、字段、时间戳)

我想要一个数据库触发器,可以随时向用户插入、更新或删除,我希望在 UsersHistory 中创建一条新记录,其中包含用户 ID 和已完成的操作(插入新的、更新的字段、删除的 ID。基本上是审计日志表。

这是我走了多远,但我不知道该怎么做:

  1. 获取修改和删除的 id 以及
  2. 如何获取已更改的字段列表和已提交的操作(插入、删除、更新)

CREATE TRIGGER Update_Users_History 
   ON  Users
   AFTER INSERT,DELETE,UPDATE
 AS 
 BEGIN
-- Insert statements for trigger here

insert into UsersHistory (user_id, [action], [fields], timestamp)
select  max(id) as user_id, {action ??},{fields??}  getdate() from Users)

END
GO

有什么建议吗?

【问题讨论】:

  • 不,你不应该依赖底层的“用户”表——你需要确保使用触发器主体中可用的InsertedDeleted“伪表”
  • 另外,您似乎相信每次 INSERT 或 UPDATE 都会调用一次触发器 - 这是 NOT 的情况。如果您 UPDATE 一大堆行(20、50 行),它将被准确地调用一次操作(插入和删除的表各有 50 行)。

标签: sql sql-server database triggers


【解决方案1】:

最简单的方法可能是简单地创建三个触发器 - 每个操作一个:

CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
   INSERT INTO dbo.UserHistory............
END     

CREATE TRIGGER trgUserDelete
ON dbo.User AFTER DELETE
AS BEGIN
   INSERT INTO dbo.UserHistory............
END 

CREATE TRIGGER trgUserUpdate
ON dbo.User AFTER UPDATE
AS BEGIN
   INSERT INTO dbo.UserHistory............
END 

这样,事情变得简单,您可以轻松了解自己在做什么,此外,它还使您能够关闭单个操作的触发器,例如:需要插入或删除大量项目。

在触发器内部,您有两个“伪表”——Inserted(用于插入和更新)和Deleted(用于更新和删除)。这些伪表包含新插入的值(或 UPDATE 中的更新值),或已删除(用于 DELETE)或已更新的值(更新前的旧值,用于 UPDATE 操作)。

您需要注意,即使您更新大量行,触发器也会被调用一次,例如InsertedDeleted 通常包含多行。

作为示例,您可以像这样编写“AFTER INSERT”触发器(只是猜测您的表结构可能是什么......):

CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
   INSERT INTO 
      dbo.UserHistory(UserID, Action, DateTimeStamp, AuditMessage)
      SELECT 
         i.UserID, 'INSERT', getdate(), 'User inserted into table'
      FROM
         Inserted i
END     

您正在寻找一种方法来找出此触发器导致的“动作”?我看不出有任何方法可以做到这一点 - 将三个触发器分开的另一个原因。找出这一点的唯一方法是计算InsertedUpdated 表中的行数:

  • 如果两个计数都大于零,则为UPDATE
  • 如果Inserted 表有行,但Deleted 没有,则它是INSERT
  • 如果Inserted 表没有行,但Deleted 有,则它是DELETE

您还在寻找“已更新字段的列表”——同样,您不会有任何简单的解决方案,真的。您可以只遍历“用户”表中感兴趣的字段,然后检查

IF UPDATE(fieldname) ......

但这有点乏味。

或者您可以使用COLUMNS_UPDATED() 函数 - 但是,这不会为您提供一个很好的列名列表,而是一个 VARBINARY,其中每一列基本上都是一个位,如果它被打开,则该列已更新。不太好用.....


如果您真的想创建一个单一的大触发器,这可以作为基础 - 它检测导致触发器触发的操作,并将条目插入到您的 User_History 表中:

CREATE TRIGGER trgUser_Universal
ON dbo.Users
AFTER INSERT, UPDATE, DELETE
AS BEGIN
  DECLARE @InsHasRows BIT = 0
  DECLARE @DelHasRows BIT = 0

  IF EXISTS(SELECT TOP 1 * FROM INSERTED)
    SET @InsHasRows = 1

  IF EXISTS(SELECT TOP 1 * FROM DELETED)
    SET @DelHasRows = 1

  DECLARE @TriggerAction VARCHAR(20)

  IF @InsHasRows = 1 AND @DelHasRows = 1 
     SET @TriggerAction = 'UPDATE'
  ELSE
     IF @InsHasRows = 1
        SET @TriggerAction = 'INSERT'
     ELSE  
        SET @TriggerAction = 'DELETE'

  IF @InsHasRows = 1
    INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
      SELECT i.UserId, @TriggerAction, null, getdate()
      FROM INSERTED i
  ELSE  
    INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
      SELECT d.UserId, @TriggerAction, null, getdate()
      FROM DELETED d
END

我还没有包括确定哪些字段已更新的部分 - 这留给读者作为练习:-)


这有帮助吗?

【讨论】:

  • 我绝对同意将它们分开,让事情更容易处理。 +1
  • 这里有个小问题:如果操作是MERGE,会发生什么?可能会有更新和插入,但我认为这个触发器最终会将它们全部标记为UPDATE
  • @aaronaught:好问题 - MERGE 被分解为单独的操作(插入、更新、删除),但老实说,我不知道它们在触发器中最终会是什么样子。我同意这很可能是更新,因为您很可能会插入一些行和删除一些行
【解决方案2】:

触发器中使用了两个“表”。一个是DELETED,一个是INSERTED。当您删除一行时,该行将被捕获到DELETED 表中。当您插入一行时,该行将被捕获到INSERTED 表中。更新行时,旧行在DELETED 表中,新行在INSERTED 表中。 DELETEDINSERTED 表与要添加触发器的表具有相同的架构。

您可以查看this solution,它将为您创建一个查询,该查询将生成您想要的所有审计触发器,以及用于存储审计的表,不包括任何选定的表。它只会触发UPDATE,但可以轻松修改为也触发INSERTDELETE

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2015-09-04
    • 2019-09-12
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    相关资源
    最近更新 更多