我想知道是否无论如何我可以在两个表上添加一个触发器,将数据复制到另一个表。
例如:
我有两个用户表,users_V1 和 users_V2,当用户使用 V1 应用程序之一更新时,它也会激活触发器在 users_V2 中更新它。
如果我想在 V2 表上添加相同的触发器,以便在 V2 中更新用户时更新 V1 中的数据,它会进入无限循环吗?有什么办法可以避免。
我想知道是否无论如何我可以在两个表上添加一个触发器,将数据复制到另一个表。
例如:
我有两个用户表,users_V1 和 users_V2,当用户使用 V1 应用程序之一更新时,它也会激活触发器在 users_V2 中更新它。
如果我想在 V2 表上添加相同的触发器,以便在 V2 中更新用户时更新 V1 中的数据,它会进入无限循环吗?有什么办法可以避免。
我不建议在处理过程中明确禁用触发器——这可能会导致奇怪的副作用。
在触发器中检测(和防止)循环的最可靠方法是使用CONTEXT_INFO()
.
例子:
CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS
DECLARE @ctx VARBINARY(128)
SELECT @ctx = CONTEXT_INFO()
IF @ctx = 0xFF
RETURN
SET @ctx = 0xFF
-- Trigger logic goes here
有关更详细的示例,请参阅此链接。
CONTEXT_INFO()
SQL Server 2000 中的注意事项:
支持上下文信息,但显然该CONTEXT_INFO
功能不支持。你必须改用这个:
SELECT @ctx = context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID
要么使用TRIGGER_NESTLEVEL()来限制触发器递归,要么
检查目标表是否需要更新:
IF (SELECT COUNT(1)
FROM users_V1
INNER JOIN inserted ON users_V1.ID = inserted.ID
WHERE users_V1.field1 <> inserted.field1
OR users_V1.field2 <> inserted.field2) > 0 BEGIN
UPDATE users_V1 SET ...
我有同样的问题。我尝试使用 CONTEXT_INFO() 但这是一个会话变量,所以它只在第一次工作!然后下次在会话期间触发触发器时,这将不起作用。因此,我最终使用了一个变量,该变量在每个受影响的触发器中返回 Nest Level 以退出。
CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS
BEGIN
--Prevents Second Nested Call
IF @@NESTLEVEL>1 RETURN
--Trigger logic goes here
END
注意:如果要停止所有嵌套调用,请使用 @@NESTLEVEL>0
另一个注意事项——在这篇文章中似乎有很多关于嵌套调用和递归调用的混淆。原始海报指的是嵌套触发器,其中一个触发器会导致另一个触发器触发,这将导致第一个触发器再次触发,依此类推。这是嵌套的,但根据 SQL Server,它不是递归的,因为触发器不是直接调用/触发自身。递归不是“一个触发器[正在]调用另一个”的地方。那是嵌套的,但不一定是递归的。您可以使用此处提到的一些设置启用/禁用递归和嵌套来测试这一点:关于嵌套的博客文章
对于这个特定的设计场景,我支持无触发器阵营。话虽如此,但我对您的应用程序的功能以及它为什么这样做的了解有限,以下是我的总体分析:
在表上使用触发器的优点是能够对表上的所有操作进行操作。就是这样,在这种情况下你的主要好处。但这意味着您的用户可以直接访问表或对表有多个访问点。我倾向于避免这种情况。触发器有它们的位置(我经常使用它们),但它是我最后使用的数据库设计工具之一,因为它们往往不太了解它们的上下文(通常是一种优势)以及在它们确实需要的地方使用时了解不同的上下文和整体用例,它们的好处被削弱了。
如果两个应用程序版本都需要触发相同的操作,它们都应该调用相同的存储过程。存储过程可以确保完成所有适当的工作,并且当您的应用不再需要支持 V1 时,可以删除存储过程的那部分。
在您的客户端代码中调用两个存储过程是一个坏主意,因为这是数据库可以轻松一致地提供数据服务的抽象层,而您的应用程序无需担心它。
我更喜欢使用视图或 UDF 或 SP 来控制与基础表的接口。用户永远无法直接访问表。这里的另一点是,您可以在用户甚至不知道的情况下呈现单个“用户”视图或 UDF 合并适当的基础表 - 可能达到甚至不需要任何“同步”的地步,因为新属性位于EAV 系统,如果您需要那种病理灵活性或其他一些仍然可以加入的不同结构 - 比如说 OUTER APPLY UDF 等。
您将不得不在触发器中创建某种环回检测。也许在将记录输入下一个表之前使用“如果存在”语句来查看记录是否存在。听起来它确实会按照当前设置的方式进入无限循环。
触发器中的递归,即一个触发器调用另一个触发器,限制为32 级
在每个触发器中,只需检查您要插入的行是否已经存在。
例子
CREATE TRIGGER Table1_Synchronize_Update ON [Table1] FOR UPDATE AS
BEGIN
UPDATE Table2
SET LastName = i.LastName
, FirstName = i.FirstName
, ... -- Every relevant field that needs to stay in sync
FROM Table2 t2
INNER JOIN Inserted i ON i.UserID = t2.UserID
WHERE i.LastName <> t2.LastName
OR i.FirstName <> t2.FirstName
OR ... -- Every relevant field that needs to stay in sync
END
CREATE TRIGGER Table1_Synchronize_Insert ON [Table1] FOR INSERT AS
BEGIN
INSERT INTO Table2
SELECT i.*
FROM Inserted i
LEFT OUTER JOIN Table2 t2 ON t2.UserID = i.UserID
WHERE t2.UserID IS NULL
END
CREATE TRIGGER Table2_Synchronize_Update ON [Table2] FOR UPDATE AS
BEGIN
UPDATE Table1
SET LastName = i.LastName
, FirstName = i.FirstName
, ... -- Every relevant field that needs to stay in sync
FROM Table1 t1
INNER JOIN Inserted i ON i.UserID = t1.UserID
WHERE i.LastName <> t1.LastName
OR i.FirstName <> t1.FirstName
OR ... -- Every relevant field that needs to stay in sync
END
CREATE TRIGGER Table2_Synchronize_Insert ON [Table2] FOR INSERT AS
BEGIN
INSERT INTO Table1
SELECT i.*
FROM Inserted i
LEFT OUTER JOIN Table1 t1 ON t1.UserID = i.UserID
WHERE t1.UserID IS NULL
END
避免像瘟疫这样的触发器......使用存储过程来添加用户。如果这需要一些设计更改,请进行更改。触发器是邪恶的。
尝试类似的东西(我没有打扰创建触发器的东西,因为您显然已经知道如何编写该部分):
update t
set field1 = i.field1
field2 = i.field2
from inserted i
join table1 t on i.id = t.id
where field1 <> i.field1 OR field2 <> i.field2