Ian*_*oyd 59 sql sql-server sql-server-2008-r2
我执行UPDATE
与OUTPUT
查询:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
Run Code Online (Sandbox Code Playgroud)
这句话很好,很好; 直到在表上定义了一个触发器.然后我的UPDATE
陈述将得到错误334:
如果语句包含没有INTO子句的OUTPUT子句,则DML语句的目标表'BatchReports'不能具有任何已启用的触发器
现在,SQL Server团队在博客文章中解释了这个问题:
错误消息是不言自明的
他们还提供解决方案:
该应用程序已更改为使用INTO子句
除了我无法做出整篇博文的正面或反面.
所以让我问一下我的问题:我应该改变什么UPDATE
才能使它有效?
Mar*_*ith 45
要解决这个限制,你需要 OUTPUT INTO ...
做点什么.例如,从中声明一个中间表变量作为目标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
Run Code Online (Sandbox Code Playgroud)
As cautioned in the other answer if your trigger writes back to the rows modified by the UPDATE
statement itself in such a way that it affects the columns that you are OUTPUT
-ing then you may not find the results useful but this is only a subset of triggers. The above technique works fine in other cases, such as triggers recording to other tables for audit purposes, or returning inserted identity values even if the original row is written back to in the trigger.
Ian*_*oyd 35
可见性警告:不要使用最高投票答案.它会给出不正确的值.继续阅读它的错误方式.
考虑到在SQL Server 2008 R2中UPDATE
使用OUTPUT
工作所需的kludge ,我改变了我的查询:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
Run Code Online (Sandbox Code Playgroud)
至:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Run Code Online (Sandbox Code Playgroud)
基本上我停止使用OUTPUT
.这并不是因为实体框架本身使用这个相同的黑客!
希望2012 2014 2016 2018将有更好的实施.
我们开始的问题是尝试使用该OUTPUT
子句来检索表中的"after"值:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Run Code Online (Sandbox Code Playgroud)
然后在SQL Server中遇到众所周知的限制(不会修复错误):
如果语句包含没有INTO子句的OUTPUT子句,则DML语句的目标表'BatchReports'不能具有任何已启用的触发器
所以我们尝试使用中间TABLE
变量来保存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
Run Code Online (Sandbox Code Playgroud)
除非失败,因为您不允许timestamp
在表中插入一个(即使是临时表变量).
我们秘密地知道a 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
Run Code Online (Sandbox Code Playgroud)
这是有效的,除了价值是错误的.
RowVersion
我们返回的时间戳不是UPDATE完成后存在的时间戳值:
0x0000000001B71692
0x0000000001B71693
这是因为OUTPUT
我们表中的值不是 UPDATE语句末尾的值:
这意味着:
任何修改行中任何值的触发器都是如此.OUTPUT将不会在UPDATE结束时输出该值.
这意味着您不信任OUTPUT返回任何正确的值
这个痛苦的现实记录在BOL中:
从OUTPUT返回的列反映了INSERT,UPDATE或DELETE语句完成之后但在执行触发器之前的数据.
.NET实体框架使用rowversion进行乐观并发.EF取决于在timestamp
发出UPDATE后知道它的值.
由于您无法使用OUTPUT
任何重要数据,因此Microsoft的Entity Framework使用与我相同的解决方法:
为了检索after值,实体框架问题:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
Run Code Online (Sandbox Code Playgroud)
不要用OUTPUT
.
是的,它有竞争条件,但这是最好的SQL Server可以做到的.