在更新触发器触发之前检查约束违规

时间:2014-11-05 17:52:20

标签: sql-server sql-server-2008 tsql check-constraints database-trigger

我的表格中有bit列和相应的datetime2列,用于跟踪设置该标志的时间:

CREATE TABLE MyTable
(
    Id int primary key identity,
    Processed bit not null,
    DateTimeProcessed datetime2
)

我添加了一个检查约束,如下所示:

ALTER TABLE MyTable 
  ADD CHECK ((Processed = 0 AND DateTimeProcessed IS NULL) 
             OR (Processed = 1 AND DateTimeProcessed IS NOT NULL))

我尝试使用DateTimeProcessed触发器控制AFTER UPDATE列的设置:

CREATE TRIGGER tr_MyTable_AfterUpdate ON MyTable
AFTER UPDATE
AS
BEGIN
    IF(UPDATE(Processed))
    BEGIN
        UPDATE MyTable
        SET DateTimeProcessed = CASE
            WHEN tab.Processed = 1 THEN GETDATE()
            ELSE NULL
            END
        FROM MyTable tab
        JOIN INSERTED ins
        ON ins.Id = tab.Id
    END
END

这样做的问题是在AFTER UPDATE触发器运行之前强制执行检查约束,因此在Processed列更新时违反了约束。

实现我在这里尝试做的最好的方法是什么?

1 个答案:

答案 0 :(得分:2)

现在,根据CREATE TABLE的MSDN页面:

  

如果表具有FOREIGN KEY或CHECK CONSTRAINTS和触发器,则在执行触发器之前评估约束条件。

这排除了使用“INSTEAD OF”触发器的可能性。

您应该删除CHECK CONSTRAINT,因为最终不需要它,因为AFTER触发器本身可以提供相同的规则强制执行:

  • 您已确定在BIT字段设置为1时设置了日期字段。
  • 您的CASE语句已经通过将日期字段清空来处理将BIT字段设置为0。
  • 您可以使用另一个块来检查IF UPDATE(DateTimeProcessed),然后将其放回DELETED表中的内容或抛出错误。

    • 如果将其更新回原始值,则可能需要测试递归触发器调用,如果是递归调用则退出。
    • 如果您想抛出错误,只需使用以下内容:

      IF(UPDATE(DateTimeProcessed))
      BEGIN
         RAISERROR('Update of [DateTimeProcessed] field is not allowed.', 16, 1);
         ROLLBACK; -- cancel the UPDATE statement
         RETURN;
      END;
      

      请记住,UPDATE()函数仅表示该字段位于UPDATE语句中;它表示值的变化。因此,进行更新,其中SET DateTimeProcessed = DateTimeProcessed显然不会更改值,但会导致UPDATE(DateTimeProcessed)返回“true”。

    • 您还可以使用列级DENY处理触发器之外的“规则”部分:

      DENY UPDATE ON MyTable (DateTimeProcessed) TO {User and/or Role};

相关问题