触发器在表上时,不能使用带有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
所以我们尝试使用中间TABLE
variables来保存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可以做的。