【问题标题】:Trigger AFTER INSERT, UPDATE, DELETE to call stored procedure with table name and primary key触发 AFTER INSERT、UPDATE、DELETE 以调用具有表名和主键的存储过程
【发布时间】:2019-03-07 03:36:51
【问题描述】:

对于同步过程,我的 SQL Server 数据库应记录已更改的列表项 - 表名和主键。

数据库已经有一个表和存储过程来执行此操作:

EXEC @ErrCode = dbo.SyncQueueItem "tableName", 1234;

我想在表中添加触发器以在 INSERT、UPDATE、DELETE 上调用此存储过程。我如何获得钥匙?最简单可行的方法是什么?

CREATE TABLE new_employees
(
    id_num INT IDENTITY(1,1),
    fname VARCHAR(20),
    minit CHAR(1),
    lname VARCHAR(30)
);
GO

IF OBJECT_ID ('dbo.sync_new_employees','TR') IS NOT NULL 
    DROP TRIGGER sync_new_employees;
GO

CREATE TRIGGER sync_new_employees
ON new_employees
AFTER INSERT, UPDATE, DELETE
AS
     DECLARE @Key Int;
     DECLARE @ErrCode Int;

     --  How to get the key???
     SELECT @Key = 12345; 

     EXEC @ErrCode = dbo.SyncQueueItem "new_employees", @key;
GO

【问题讨论】:

  • 您知道 SQL Server 已经内置了用于更改跟踪、复制等的机制...
  • @Damien_The_Unbeliever 我们在这里都很聪明。你将不得不更加努力地摆姿势......

标签: sql-server triggers


【解决方案1】:

访问由操作更改的记录的方法是使用 SQL Server 提供给您的 InsertedDeleted 伪表。

Inserted 包含任何插入的记录,或任何带有新值的更新记录。

Deleted 包含任何已删除的记录,或任何带有旧值的更新记录。

More Info

在编写触发器时,为了安全起见,应始终针对多条记录被执行的情况编写代码。不幸的是,如果您需要调用意味着循环的 SP - 这并不理想。

以下代码显示了如何为您的示例完成此操作,并包含一种检测操作是否为插入/更新/删除的方法。

declare @Key int, @ErrCode int, @Action varchar(6);

declare @Keys table (id int, [Action] varchar(6));

insert into @Keys (id, [Action])
  select coalesce(I.id, D.id_num)
    , case when I.id is not null and D.id is not null then 'Update' when I.id is not null then 'Insert' else 'Delete' end
  from Inserted I
  full join Deleted D on I.id_num = D.id_num;

while exists (select 1 from @Keys) begin
  select top 1 @Key = id, @Action = [Action] from @Keys;
  exec @ErrCode = dbo.SyncQueueItem 'new_employees', @key;
  delete from @Keys where id = @Key;
end

进一步:除了解决您指定的问题之外,还有几点值得注意。

  1. 正如@Damien_The_Unbeliever 指出的那样,有内置机制来完成更改跟踪,效果会更好。
  2. 如果您仍然希望处理自己的更改跟踪,如果您可以安排它以便一次性处理整个记录集而不是执行逐行操作,它会执行得更好。有两种方法可以做到这一点 a) 将您的更改跟踪代码移动到触发器内,并且不要使用 SP。 b) 使用“用户定义的表类型”将更改记录集传递给 SP。

【讨论】:

    【解决方案2】:

    您应该使用魔术表来获取数据。 通常,插入和删除的表在触发器的上下文中称为魔术表。 SQL Server 中有 Inserted 和 Deleted 魔术表。这些表由 SQL Server 在内部自动创建和管理,用于在对数据库表进行 DML 操作(插入、更新和删除)期间保存最近插入、删除和更新的值。

    插入的魔术表

    Inserted 表保存最近插入的值,即新数据值。因此,最近添加的记录被插入到 Inserted 表中。

    已删除魔法表

    Deleted 表保存最近删除或更新的值,即旧数据值。因此,旧的更新和删除记录被插入到 Deleted 表中。

    **可以使用插入和删除的魔表获取id_num的值**

     SELECT top 1 @Key = id_num from inserted  
    

    注意:此代码示例仅适用于插入场景的单个记录。对于批量插入/更新方案,您需要从存储在临时表或变量中的插入和删除表中获取记录,然后循环遍历它以传递给您的过程,或者您可以将表变量传递给您的过程并在那里处理多条记录。

    【讨论】:

    • 我猜你是在假设/暗示使用某种形式的循环来处理多条记录,对吧?
    • @Dale Burrell,是的,谢谢,让我更新我的答案。
    • 只处理单个记录插入/更新/删除的情况被认为是不好的编码实践。
    • 同意,这只是一个解释它的示例代码,我已经对你的回答投了赞成票。
    【解决方案3】:

    DML 触发器应该操作 set 数据,否则只会处理一行。它可以是这样的。当然也可以使用 魔术表 inserteddeleted

    CREATE TRIGGER dbo.tr_employees 
       ON  dbo.employees --the table from Northwind database
       AFTER INSERT,DELETE,UPDATE
    AS 
    BEGIN
        -- SET NOCOUNT ON added to prevent extra result sets from
        -- interfering with SELECT statements.
        SET NOCOUNT ON;
            declare @tbl table (id int identity(1,1),delId int,insId int)
    
            --Use "magic tables" inserted and deleted
            insert @tbl(delId, insId)
            select d.EmployeeID, i.EmployeeID
            from inserted i --empty when "delete"
            full join deleted d --empty when "insert"
                on i.EmployeeID=d.EmployeeID
    
            declare @id int,@key int,@action char
    
            select top 1 @id=id, @key=isnull(delId, insId),
                @action=case 
                    when delId is null then 'I' 
                    when insId is null then 'D' 
                    else 'U' end --just in case you need the operation executed
            from @tbl
            --do something for each row
            while @id is not null --instead of cursor
            begin
                --do the main action
                --exec dbo.sync 'employees', @key, @action
                --remove processed row
                delete @tbl where id=@id
                --refill @variables
                select top 1 @id=id, @key=isnull(delId, insId),
                    @action=case 
                        when delId is null then 'I' 
                        when insId is null then 'D' 
                        else 'U' end --just in case you need the operation executed
                from @tbl
            end
    END
    

    【讨论】:

      【解决方案4】:

      不是最好的解决方案,而只是对问题的直接回答

      SELECT @Key = COALESCE(deleted.id_num,inserted.id_num);
      

      不是最好的方法(如果不是最坏的方法)(不要在家里尝试这个),但至少这将有助于多个值

      DECLARE @Key INT;
      DECLARE triggerCursor CURSOR LOCAL FAST_FORWARD READ_ONLY 
          FOR SELECT COALESCE(i.id_num,d.id_num) AS [id_num]
              FROM inserted i
              FULL JOIN deleted d ON d.id_num = i.id_num
              WHERE (
                      COALESCE(i.fname,'')<>COALESCE(d.fname,'')
                   OR COALESCE(i.minit,'')<>COALESCE(d.minit,'')
                   OR COALESCE(i.lname,'')<>COALESCE(d.lname,'')
              )
      ;
      
      OPEN triggerCursor;
      FETCH NEXT FROM triggerCursor INTO @Key;
      WHILE @@FETCH_STATUS = 0
      BEGIN
          EXEC @ErrCode = dbo.SyncQueueItem 'new_employees', @key;
          FETCH NEXT FROM triggerCursor INTO @Key;
      END
      
      CLOSE triggerCursor;
      DEALLOCATE triggerCursor;
      

      使用基于触发器的“value-change-tracker”的更好方法:

      INSERT INTO [YourTableHistoryName] (id_num, fname, minit, lname, WhenHappened)
      SELECT COALESCE(i.id_num,d.id_num) AS [id_num]
          ,i.fname,i.minit,i.lname,CURRENT_TIMESTAMP AS [WhenHeppened]
      FROM inserted i
      FULL JOIN deleted d ON d.id_num = i.id_num
      WHERE ( COALESCE(i.fname,'')<>COALESCE(d.fname,'')
          OR COALESCE(i.minit,'')<>COALESCE(d.minit,'')
          OR COALESCE(i.lname,'')<>COALESCE(d.lname,'')
      )
      ;
      

      最好(在我看来)跟踪更改的方法是使用临时表(SQL Server 2016+)

      【讨论】:

      • 只处理单个记录插入/更新/删除的情况被认为是不好的编码实践。
      • 我知道。直接回答所提出的问题可能是个坏主意。
      • @MichaelCole - 我相信您知道这不是一个好的解决方案 - 如果您曾经对多行进行操作,它将无法正常工作。
      • 更新了我的答案
      【解决方案5】:

      在触发器中插入/删除将生成与触摸一样多的行,并且每个键调用存储的过程将需要每行一个游标或类似方法。 您应该检查 SQL Server 中的时间戳/行版本。您可以将其添加到所有有问题的表中(不为空、自动递增、每个表/行在数据库中唯一等)。 您可以将该列上的唯一索引添加到您添加该列的所有表中。 @@DBTS 是当前时间戳,您可以存储今天的@@DBTS,明天您将扫描从该到当前@@DBTS 的所有表。对于所有更新和插入,时间戳/行版本将递增,但对于删除它不会跟踪,对于删除,您可以使用仅删除触发器并将键插入不同的表。 更改数据捕获或更改跟踪可以更轻松地做到这一点,但如果服务器上的容量很大或数据加载量很大,扫描事务日志的分区切换就会成为瓶颈,在某些情况下,您必须删除更改数据捕获才能保存事务日志不会无限增长。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-25
        • 1970-01-01
        • 2021-08-09
        • 2019-01-27
        • 2011-01-15
        • 1970-01-01
        • 1970-01-01
        • 2013-08-29
        相关资源
        最近更新 更多