触发AFTER INSERT,UPDATE,DELETE以使用表名和主键调用存储过程

时间:2019-03-07 03:36:51

标签: sql-server triggers

对于同步过程,我的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

5 个答案:

答案 0 :(得分:4)

访问由操作更改的记录的方法是使用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。

答案 1 :(得分:1)

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

插入魔术桌

Inserted表保存了最近插入的值,换句话说,就是新的数据值。因此,最近添加的记录将插入到“插入的”表中。

已删除魔术表

“已删除”表保存最近删除或更新的值,即旧数据值。因此,将旧的已更新和已删除的记录插入“已删除”表中。

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

 SELECT top 1 @Key = id_num from inserted  

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

答案 2 :(得分:1)

DML触发器应操作设置数据,否则将仅处理一行。可能是这样的。当然,请使用魔法表 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

答案 3 :(得分:0)

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

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;

使用基于触发器的“值更改跟踪器”的更好方法:

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 +)

答案 4 :(得分:0)

插入/删除触发器中的

将生成与被触摸的行一样多的行,并且每个键调用存储的proc将需要每行使用游标或类似的方法。 您应该在SQL Server中检查时间戳/行版本。您可以将其添加到所有相关表中(不为null,自动递增,每个表/行在数据库中唯一)等。 您可以在该列上为添加该列的所有表添加唯一索引。 @@ DBTS是当前时间戳,您可以存储今天的@@ DBTS,明天将扫描从该表到当前@@ DBTS的所有表。对于所有更新和插入,时间戳/行版本将增加,但对于删除将不会跟踪,对于删除,您可以使用仅删除触发器并将插入键插入其他表中。 更改数据捕获或更改跟踪可以使此操作更容易,但是如果服务器上的数据量很大或数据负载大量,则分区开关扫描事务日志将成为瓶颈,在某些情况下,您必须删除更改数据捕获才能保存交易日志的增长不确定。