SQL Server 触发器循环

发布于 2024-08-20 13:28:25 字数 235 浏览 4 评论 0原文

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

例如:

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

  • 如果我想在V2表上添加相同的触发器,以便在V2中更新用户时更新V1中的数据,会不会陷入无限循环?有没有办法避免这种情况。

I would like to know if there is anyway I can add a trigger on two tables that will replicate the data to the other.

For example:

  • I have a two users tables, users_V1 and users_V2, When a user is updated with one of the V1 app, it activate a trigger updating it in users_V2 as well.

  • If I want to add the same trigger on the V2 table in order to update the data in V1 when a user is updated in V2, will it go into an infinite loop? Is there any way to avoid that.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

倾`听者〃 2024-08-27 13:28:25

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

检测(和防止)触发器中的循环的最可靠方法是使用 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

有关更详细的示例,请参阅此链接


关于 SQL Server 2000 中 CONTEXT_INFO() 的注意事项:

支持上下文信息,但显然不支持 CONTEXT_INFO 函数。你必须改用这个:

SELECT @ctx = context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

I don't recommend explicitly disabling the trigger during processing - this can cause strange side-effects.

The most reliable way to detect (and prevent) cycles in a trigger is to use CONTEXT_INFO().

Example:

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

See this link for a more detailed example.


Note on CONTEXT_INFO() in SQL Server 2000:

Context info is supported but apparently the CONTEXT_INFO function is not. You have to use this instead:

SELECT @ctx = context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID
沉鱼一梦 2024-08-27 13:28:25
  • 使用 TRIGGER_NESTLEVEL() 限制触发器递归,或者 < /p>

  • < p>检查目标表是否需要更新:

    IF(选择计数(1) 
    来自用户_V1 
    内连接插入用户_V1.ID = 插入.ID
    WHERE users_V1.field1 <>插入字段1
    或 users_V1.field2 <>插入.field2) > 0 开始
    
    更新 users_V1 SET ...
    
  • Either use TRIGGER_NESTLEVEL() to restrict trigger recursion, or

  • check the target table whether an UPDATE is necessary at all:

    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 ...
    
柠檬 2024-08-27 13:28:25

我遇到了完全相同的问题。我尝试使用 CONTEXT_INFO() 但这是一个会话变量,因此它仅在第一次有效!然后下次在会话期间触发触发器时,这将不起作用。因此,我最终使用了一个变量,该变量在每个受影响的触发器中返回嵌套级别以退出。

示例:

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,不是递归的,因为触发器不直接调用/触发自身。递归并不是“一个触发器调用另一个触发器”。这是嵌套的,但不一定是递归的。您可以通过启用/禁用递归和嵌套来测试这里提到的一些设置:有关嵌套的博客文章

I had the exact same problem. I tried using CONTEXT_INFO() but that is a session variable and so it works only the first time! Then next time a trigger fires during the session, this won't work. So I ended up with using a variable that returns Nest Level in each of the affected triggers to exit.

Example:

CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS
BEGIN
      --Prevents Second Nested Call
      IF @@NESTLEVEL>1 RETURN 

      --Trigger logic goes here
END

Note: Or use @@NESTLEVEL>0 if you want to stop all nested calls

One other note -- There seems to be much confusion in this article about nested calls and recursive calls. The original poster was referring to a nested trigger where one trigger would cause another trigger to fire, which would cause the first trigger to fire again, and so on. This is Nested, but according to SQL Server, not recursive because the trigger is not calling/triggering itself directly. Recursion is NOT where "one trigger [is] calling another". That is nested, but not necessarily recursive. You can test this by enabling/disabling recursion and nesting with some settings mentioned here: blog post on nesting

吾家有女初长成 2024-08-27 13:28:25

对于这个特定的设计场景,我属于无触发阵营。话虽如此,由于我对您的应用程序的功能和原因了解有限,以下是我的总体分析:

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

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

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

我更喜欢更多地控制底层表的接口 - 使用视图、UDF 或 SP。用户永远无法直接访问表。这里的另一点是,您可以呈现单个“用户”VIEW 或 UDF 合并适当的基础表,而用户甚至不知道 - 也许甚至不需要任何“同步”,因为新属性位于EAV 系统,如果您需要那种病态的灵活性或仍然可以连接的其他不同结构 - 例如 OUTER APPLY UDF 等。

I'm with the no triggers camp for this particular design scenario. Having said that, with the limited knowledge I have about what your app does and why it does it, here's my overall analysis:

Using a trigger on a table has an advantage of being able to act on all actions on the table. That's it, your main benefit in this case. But that would mean you have users with direct access to the table or multiple access points to the table. I tend to avoid that. Triggers have their place (I use them a lot), but it's one of the last database design tools I use because they tend to not know a lot about their context (generally, a strength) and when used in a place where they do need to know about different contexts and overall use cases, their benefits are weakened.

If both app versions need to trigger the same action, they should both call the same stored proc. The stored proc can ensure that all the appropriate work is done, and when your app no longer needs to support V1, then that part of the stored proc can be removed.

Calling two stored procs in your client code is a bad idea, because this is an abstraction layer of data services which the database can provide easily and consistently, without your application being worried about it.

I prefer to control the interface to the underlying tables more - with either views or UDFs or SPs. Users never get direct access to a table. Another point here is that you could present a single "users" VIEW or UDF coalescing the appropriate underlying tables without the user even knowing about - perhaps getting to the point where there is not even any "synchronization" necessary, since new attributes are in an EAV system if you need that kind of pathological flexibility or in some other different structure which can still be joined - say OUTER APPLY UDF etc.

伴我老 2024-08-27 13:28:25

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

You're going to have to create some sort of loopback detection within your trigger. Perhaps using an "if exists" statement to see if the record exists before entering it into the next table. It does sound like it will go into an infinite loop the way it's currently set up.

×纯※雪 2024-08-27 13:28:25

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

Avoid triggers like the plague .... use a stored procedure to add the user. If this requires some design changes then make them. Triggers are the EVIL.

我做我的改变 2024-08-27 13:28:25

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

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

Try something like (I didn;t bother with thecreate trigger stuff as you clearly already know how to write that part):

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
在梵高的星空下 2024-08-27 13:28:25

触发器中的递归,即一个触发器调用另一个触发器,仅限于 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

Recursion in triggers, that is, one trigger calling another, is limited to 32 levels

In each trigger, just check if the row you wish to insert already exists.

Example

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文