【问题标题】:SQL Server - Inserted TableSQL Server - 插入表
【发布时间】:2013-04-09 14:53:16
【问题描述】:

我正在使用以下触发器检查插入/删除表的输出,如何在验证列后将拦截的 UPDATE 命令传递给服务器?

CREATE TRIGGER Test1_LastUpdate ON Test1
INSTEAD OF UPDATE 
AS
    SELECT * FROM Inserted
    SELECT * FROM Deleted
GO

编辑:我正在寻找在架构更新后不需要更改的解决方案。

CREATE TRIGGER Test1_LastUpdate2 ON Test1
INSTEAD OF UPDATE
AS
    --COMMIT UPDATE THAT WAS INTERCEPTED
END

【问题讨论】:

  • 你的意思是它不会匹配提交到表的内容?您通过拦截UPDATE 来阻止它提交到表中,但是如果您不拦截它,则插入的记录应该是表中将要更新的内容。听起来你想要一个触发器FOR UPDATE
  • @Love2Learn 我了解我正在拦截更新命令。我正在寻找一种在数据进入数据库之前检查数据的方法。我想要的是检查插入/删除的表,然后根据某些条件提交发送到服务器的操作。我将编辑代码以使其更清晰。

标签: sql sql-server database triggers relational-database


【解决方案1】:

“在验证列后将截获的 UPDATE 命令传递给服务器”的唯一方法是自己执行 UPDATE

选项 1 - 回滚

但是,您现在已经说过,当这些列添加到表中时,您不希望向触发器添加更多列。因此,您可以选择简单地回滚任何无效的更改。这可能看起来像这样:

CREATE TRIGGER TR_Sample_U ON dbo.Sample -- No AFTER trigger needed here!
AS
IF EXISTS ( --check for disallowed modifications
   SELECT *
   FROM
      Inserted I
      INNER JOIN Deleted D
         ON I.SampleID = D.SampleID
   WHERE
      I.Something <> D.Something
      AND I.UpdateDate = D.UpdateDate
)
ROLLBACK TRAN;

选项 2 - 在触发器中执行更新

但是,如果您需要更多地控制更新实际需要的内容,例如需要在提交之前修改值,则您必须自己执行更新。例如:

CREATE TRIGGER TR_Sample_U ON dbo.Sample
INSTEAD OF UPDATE
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;

UPDATE S
SET S.Value = I.Value + '+'
FROM
   dbo.Sample S
   INNER JOIN Inserted I
      ON S.SampleID = I.SampleID
;

这是一个不做任何检查的小例子,但你明白了——当你对Sample 表执行更新时,你会看到该值获得了一个额外的+ 字符——您的更新被拦截,Inserted 值(表示更新后建议的更改)在提交之前已被修改。

See a SQL Fiddle Demo 这个在行动。

唯一需要注意的是递归:

  1. 直接递归

    当您的更新可能导致其他触发器运行修改同一个基表时,您可以在它们之间进行乒乓操作,直到达到最大嵌套级别并回滚整个事务。因此,请注意触发器之间可能出现的乒乓现象。

  2. 间接递归

    您可能不必担心这个,因为database-level RECURSIVE TRIGGERS option 在 SQL Server 中默认是关闭的。但是,如果它打开,您可以根据新更新获得相同的触发器触发。

这些可以通过各种方式得到改善:

  • 检查触发器内的TRIGGER_NESTLEVEL,如果嵌套的深度足够,则退出触发器。

  • 为避免仅直接递归,请组合触发器。

  • 在某些特定情况下,战略性地分配哪个触发器将首先/最后运行可能会解决问题。不能指定绝对顺序,但可以选择第一个和最后一个。

请注意,乒乓问题适用于任何类型的触发器,INSTEAD OFAFTER,它们修改自己的基表,或通过另一个表参与更新链(具有修改另一个表...)最终会回来修改基表。

选项 2B - 预处理 AFTER UPDATE 触发器。

我将此选项称为 2B,因为它确实是选项 2,但具有增强功能。如果您不想每次向表中添加列时都必须手动更新触发器(我完全同意这种观点),您可以自动执行此操作。创建一个存储过程,该过程可以创建一个适当的触发器来观察您需要的所有验证。您可以将此验证的基本代码放入表中,然后在 SP 中将其选择为变量,添加 SQL 脚本通过挖掘 INFORMATION_SCHEMA.COLUMNS 视图中的信息来更新列以进行最终更新,然后最终重写触发器.这还可以附加到 DDL 触发器,因此它是 100% 自动化的:您可以从基表中添加或删除列,DDL 触发器会触发,并为您重写 DML 触发器。

听起来工作量很大,但如果您将其设计为数据驱动的,则可以将其推广到与整个数据库中的任何表一起使用,这可能具有很大的价值,具体取决于您的使用场景。

【讨论】:

  • 我希望创建一个触发器,在向数据库添加其他列时无需修改该触发器,在执行内部联接时,我必须明确每一列。我试图避免枚举所有列。
  • 好的,谢谢你让我知道——虽然如果你能在你的问题中这么说肯定会很好,所以我没有浪费时间在对你不起作用的选项上。
  • 抱歉,我为其他人修改了问题的措辞,使其不那么明确。
  • 以后请不要让问题的解释性降低。我们需要从您那里获得的所有信息,因此我们不会浪费时间,我们将免费捐赠时间来解决不属于我们自己的问题。跨度>
【解决方案2】:

为了检索 UPDATE 语句,请使用 SQLprofiler。插入的表是正在插入或更新的内容的快照(这里存储了新值)。删除的表是您正在更新/删除的值的快照。您的触发器只会被执行,而不是针对表 Test1 的更新命令,您可以看到您在插入的内容中更新了什么。

检查这个article关于触发器。

【讨论】:

    【解决方案3】:

    嗯,这是一种选择……

    /*
            Create  Table ExampleTable (LastRefreshed DateTime)
            Go
            Insert  ExampleTable
            Select  GetDate()
    */
    
    Begin   Tran
    
    If      Object_ID('tempdb..#check') Is Not Null Drop Table #check
    Create  Table #check (InsertedVal DateTime, DeletedVal DateTime)
    
    Update  ExampleTable
    Set     LastRefreshed = GetDate()
    Output  Inserted.LastRefreshed As InsertedVal, Deleted.LastRefreshed As DeletedVal Into #check
    
    Select  *
    From    #check
    
    If      Exists (Select  1
                    From    #check
                    Where   InsertedVal > DeletedVal)
    Begin
            Rollback Tran
    End
    Else
    Begin
            Commit Tran
    End
    

    这将创建一个带有 DateTime 记录的表。更新尝试将其更新为“现在”,但它在事务中运行并将其插入和删除的记录转储到临时表以供使用。更新后,您可以对表数据进行任何检查,以确定您是要提交还是回滚您的更改。出于示例目的,我写了这个总是回滚。

    【讨论】:

    • 不,你不会得到一个无限的触发循环。这是完全不正确的信息。您需要担心从触发器中触发的唯一触发器是其他触发器,而不是正在运行的触发器。
    • 谢谢@ErikE ...我从其他人那里听说过,从不费心去查找或确认它。我删除了错误信息。该选项仍然有效,但我确实希望避免使虚假信息永久存在。我会查找并教育自己。谢谢!
    • 请参阅this SQL Fiddle demonstrating。请注意,您可以在两个触发器之间获得“乒乓”,但不能在一个触发器之间。解决方案是检查TRIGGER_NESTLEVEL,如果触发器已经在正确的嵌套级别运行,则不执行任何更新。
    • @ErikE 这个错误信息似乎比我从这个人那里听到的更广泛......garrypassarella.co.uk/2012/09/24/…......我测试了它,它确实是完全错误的。再次感谢!
    • 抱歉,我没有提供完整的图片。确实,您可以在同一个触发器上进行乒乓操作,但前提是您打开默认关闭的数据库范围级别的“RECURSIVE_TRIGGERS”选项。所以我收回我之前的全局声明,它总是错误的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多