8

我想知道是否无论如何我可以在两个表上添加一个触发器,将数据复制到另一个表。

例如:

  • 我有两个用户表,users_V1 和 users_V2,当用户使用 V1 应用程序之一更新时,它也会激活触发器在 users_V2 中更新它。

  • 如果我想在 V2 表上添加相同的触发器,以便在 V2 中更新用户时更新 V1 中的数据,它会进入无限循环吗?有什么办法可以避免。

4

8 回答 8

8

我不建议在处理过程中明确禁用触发器——这可能会导致奇怪的副作用。

在触发器中检测(和防止)循环的最可靠方法是使用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
于 2010-02-10T14:52:43.363 回答
6
  • 要么使用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 ...
    
于 2010-02-10T15:27:38.723 回答
5

我有同样的问题。我尝试使用 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,它不是递归的,因为触发器不是直接调用/触发自身。递归不是“一个触发器[正在]调用另一个”的地方。那是嵌套的,但不一定是递归的。您可以使用此处提到的一些设置启用/禁用递归和嵌套来测试这一点:关于嵌套的博客文章

于 2010-03-10T22:22:13.080 回答
1

对于这个特定的设计场景,我支持无触发器阵营。话虽如此,但我对您的应用程序的功能以及它为什么这样做的了解有限,以下是我的总体分析:

在表上使用触发器的优点是能够对表上的所有操作进行操作。就是这样,在这种情况下你的主要好处。但这意味着您的用户可以直接访问表或对表有多个访问点。我倾向于避免这种情况。触发器有它们的位置(我经常使用它们),但它是我最后使用的数据库设计工具之一,因为它们往往不太了解它们的上下文(通常是一种优势)以及在它们确实需要的地方使用时了解不同的上下文和整体用例,它们的好处被削弱了。

如果两个应用程序版本都需要触发相同的操作,它们都应该调用相同的存储过程。存储过程可以确保完成所有适当的工作,并且当您的应用不再需要支持 V1 时,可以删除存储过程的那部分。

在您的客户端代码中调用两个存储过程是一个坏主意,因为这是数据库可以轻松一致地提供数据服务的抽象层,而您的应用程序无需担心它。

我更喜欢使用视图或 UDF 或 SP 来控制与基础表的接口。用户永远无法直接访问表。这里的另一点是,您可以在用户甚至不知道的情况下呈现单个“用户”视图或 UDF 合并适当的基础表 - 可能达到甚至不需要任何“同步”的地步,因为新属性位于EAV 系统,如果您需要那种病理灵活性或其他一些仍然可以加入的不同结构 - 比如说 OUTER APPLY UDF 等。

于 2010-02-10T15:54:03.967 回答
0

您将不得不在触发器中创建某种环回检测。也许在将记录输入下一个表之前使用“如果存在”语句来查看记录是否存在。听起来它确实会按照当前设置的方式进入无限循环。

于 2010-02-10T14:37:36.863 回答
0

触发器中的递归,即一个触发器调用另一个触发器,限制为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
于 2010-02-10T14:38:43.513 回答
0

避免像瘟疫这样的触发器......使用存储过程来添加用户。如果这需要一些设计更改,请进行更改。触发器是邪恶的。

于 2010-02-10T14:39:09.087 回答
0

尝试类似的东西(我没有打扰创建触发器的东西,因为您显然已经知道如何编写该部分):

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
于 2010-02-10T14:48:22.650 回答