触发器在表上时,不能使用带有OUTPUT子句的UPDATE

我使用OUTPUT查询执行UPDATE

 UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate WHERE BatchReports.BatchReportGUID = @someGuid 

这句话很好,很好。 直到在桌上定义一个触发器。 然后我的UPDATE语句会得到错误334 :

如果语句包含不带INTO子句的OUTPUT子句,则DML语句的目标表“BatchReports”不能具有任何已启用的触发器

现在,SQL Server团队在博客文章中解释了这个问题:

错误消息是不言自明的

他们也提供解决scheme:

该应用程序已更改为使用INTO子句

除了我不能整个博客文章的正面或反面。

所以让我问我的问题:我应该改变我的UPDATE ,以便它的工作?

也可以看看

  • 更新与OUTPUT子句 – 触发器和SQLMoreResults
  • 为什么MERGE语句的目标表不允许启用规则?
  • 语句包含一个没有INTO子句错误的OUTPUT子句

要解决这个限制,你需要OUTPUT INTO ...东西。 例如声明一个中间表variables作为目标,然后从那里SELECT

 DECLARE @T TABLE ( BatchFileXml XML, ResponseFileXml XML, ProcessedDate DATE, RowVersion BINARY(8) ) UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate, inserted.Timestamp INTO @T WHERE BatchReports.BatchReportGUID = @someGuid SELECT * FROM @T 

鉴于在SQL Server 2008 R2中使用OUTPUT进行UPDATE所需的kludge工作,我改变了我的查询:

 UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate WHERE BatchReports.BatchReportGUID = @someGuid 

至:

 SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports WHERE BatchReports.BatchReportGUID = @someGuid UPDATE BatchReports SET IsProcessed = 1 WHERE BatchReports.BatchReportGUID = @someGuid 

基本上我停止使用OUTPUT 。 希望2012年会有更好的实施。


更新:使用OUTPUT是有害的

我们开始的问题是试图使用OUTPUT子句来检索表中的“后”值:

 UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID WHERE BatchReports.BatchReportGUID = @someGuid 

然后在SQL Server中遇到了众所周知的限制(不可修复的bug):

如果语句包含不带INTO子句的OUTPUT子句,则DML语句的目标表“BatchReports”不能具有任何已启用的触发器

解决方法尝试#1

所以我们尝试使用中间TABLEvariables来保存OUTPUT结果:

 DECLARE @t TABLE ( LastModifiedDate datetime, RowVersion timestamp, BatchReportID int ) UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID INTO @t WHERE BatchReports.BatchReportGUID = @someGuid SELECT * FROM @t 

除了这个失败,因为你不允许在表中插入一个timestamp (甚至是一个临时表variables)。

解决方法尝试#2

我们偷偷知道一个timestamp实际上是一个64位(也就是8个字节)的无符号整数。 我们可以改变我们的临时表定义使用binary(8)而不是timestamp

 DECLARE @t TABLE ( LastModifiedDate datetime, RowVersion binary(8), BatchReportID int ) UPDATE BatchReports SET IsProcessed = 1 OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID INTO @t WHERE BatchReports.BatchReportGUID = @someGuid SELECT * FROM @t 

除了价值是错误的,这是有效的

我们返回的时间戳RowVersion不是它在UPDATE完成后存在的时间戳的值:

  • 返回的时间戳0x0000000001B71692
  • 实际时间戳0x0000000001B71693

这是因为我们表中的OUTPUT值不像UPDATE语句的末尾那样:

  • UPDATE语句开始
    • 修改行
    • 时间戳已更新
    • 检索新的时间戳
    • 触发器运行
      • 修改行
      • 时间戳已更新
  • UPDATE语句完成

意即:

  • 我们没有得到它在UPDATE语句末尾存在的时间戳
  • 我们得到时间戳,因为它在UPDATE语句的不确定中间
  • 我们没有得到正确的时间戳

任何修改行中任何值的触发器都是如此。 OUTPUT将不会输出UPDATE结束的值。

这意味着你不相信OUTPUT返回任何正确的值

BOLlogging了这个痛苦的现实:

从OUTPUT返回的列在INSERT,UPDATE或DELETE语句完成之后但在触发器执行之前反映数据。

Entity Framework如何解决它?

.NET Entity Framework使用rowversion进行乐观并发。 EF在发出UPDATE之后,依赖于知道timestamp的值。

由于不能将OUTPUT用于任何重要的数据,因此Microsoft的Entity Framework使用与我相同的解决方法:

解决方法#3 – 最终

为了检索值,entity framework问题:

 UPDATE [dbo].[BatchReports] SET [IsProcessed] = @0 WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2)) SELECT [RowVersion], [LastModifiedDate] FROM [dbo].[BatchReports] WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1 

不要使用OUTPUT

是的,它受到竞争条件的影响,但这是最好的SQL Server可以做的。