0

我正在使用以下触发器检查插入/删除表的输出,如何在验证列后将截获的 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
4

3 回答 3

2

您“在验证列后将拦截的 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 - 在触发器中执行 UPDATE

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

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值(表示更新后建议的更改)在提交之前进行了修改。

请参阅此操作的 SQL Fiddle 演示

唯一需要注意的是递归:

  1. 直接递归

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

  2. 间接递归

    您可能不必担心这一点,因为在 SQL Server 中,数据库级RECURSIVE TRIGGERS选项默认处于关闭状态。但是,如果它打开,您可以根据新更新获得相同的触发器触发。

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

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

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

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

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

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

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

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

于 2013-04-09T17:17:56.727 回答
0

嗯,这是一种选择...

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

于 2013-04-09T17:05:13.093 回答
0

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

查看这篇关于触发器的文章。

于 2013-04-09T15:12:14.580 回答