对于同步过程,我的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
答案 0 :(得分:4)
访问由操作更改的记录的方法是使用SQL Server提供的Inserted
和Deleted
伪表。
Inserted
包含所有插入的记录或任何具有新值的更新记录。
Deleted
包含所有已删除的记录或具有旧值的任何更新的记录。
为安全起见,编写触发器时,应始终对要操作多个记录的情况进行编码。不幸的是,如果您需要调用一个意味着循环的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 :(得分:1)
您应该使用魔术表来获取数据。 通常,在触发器的上下文中,插入和删除的表称为魔术表。 SQL Server中有插入和删除的魔术表。这些表由SQL Server在内部自动创建和管理,以在数据库表的DML操作(插入,更新和删除)中保存最近插入,删除和更新的值。
插入魔术桌
Inserted表保存了最近插入的值,换句话说,就是新的数据值。因此,最近添加的记录将插入到“插入的”表中。
已删除魔术表
“已删除”表保存最近删除或更新的值,即旧数据值。因此,将旧的已更新和已删除的记录插入“已删除”表中。
**您可以使用插入和删除的魔术表获取id_num的值**
SELECT top 1 @Key = id_num from inserted
注意:此代码示例仅适用于插入场景的单个记录。对于批量插入/更新方案,您需要从临时表或变量中存储的已插入和已删除表中获取记录,然后遍历该表以传递给过程,或者可以将表变量传递给过程并在那里处理多个记录。
答案 2 :(得分:1)
DML触发器应操作设置数据,否则将仅处理一行。可能是这样的。当然,请使用魔法表 inserted
和deleted
。
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的所有表。对于所有更新和插入,时间戳/行版本将增加,但对于删除将不会跟踪,对于删除,您可以使用仅删除触发器并将插入键插入其他表中。 更改数据捕获或更改跟踪可以使此操作更容易,但是如果服务器上的数据量很大或数据负载大量,则分区开关扫描事务日志将成为瓶颈,在某些情况下,您必须删除更改数据捕获才能保存交易日志的增长不确定。