我正在使用UPDATE
查询执行OUTPUT
:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
这句话很好,很好;直到在表上定义了一个触发器。然后,我的UPDATE
语句将收到错误334:
如果语句包含没有INTO子句的OUTPUT子句,则DML语句的目标表'BatchReports'不能具有任何已启用的触发器
现在a blog post by the SQL Server team:
解释了这个问题错误消息不言自明
他们也提供解决方案:
应用程序已更改为使用INTO条款
除了我无法对整个博客文章做出正面或反面。
所以让我问一下我的问题:我应该将UPDATE
更改为什么才能生效?
答案 0 :(得分:42)
要解决此限制,您需要OUTPUT INTO ...
。例如声明一个中间表变量作为目标,然后SELECT
。{/ p>
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
正如在另一个答案中提醒的那样,如果你的触发器回写到UPDATE
语句本身修改的行,它会影响你OUTPUT
的列,那么你可能不会找到有用的结果,但这只是触发器的一个子集。上述技术在其他情况下工作正常,例如触发器记录到其他表以进行审计,或者返回插入的标识值,即使原始行被写回触发器中也是如此。
答案 1 :(得分:30)
可见性警告:请勿使用highest voted answer。它会给出不正确的值。继续阅读它的错误方式。
考虑到UPDATE
OUTPUT
在SQL Server 2008 R2中工作所需的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 2014 2016 2018将有更好的实施。
我们开始使用的问题是尝试使用OUTPUT
子句检索表中的“值之后:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
然后在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
除非失败,因为您不允许在表中插入timestamp
(甚至是临时表变量)。
我们秘密地知道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语句末尾的值相同:
这意味着:
修改行中任何值的任何触发器也是如此。 OUTPUT不会在UPDATE结束时输出该值。
这意味着您不信任OUTPUT返回任何正确的值
这个痛苦的现实记录在BOL中:
从OUTPUT返回的列反映了在INSERT,UPDATE或DELETE语句完成之后但在执行触发器之前的数据。
.NET实体框架使用rowversion进行乐观并发。 EF取决于在发出更新后知道timestamp
的值。
由于您无法将OUTPUT
用于任何重要数据,因此Microsoft的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可以做到的。
答案 2 :(得分:1)
为什么将所有需要的列放入表变量?我们只需要主键,就可以在UPDATE之后读取所有数据。使用交易时没有种族:
DECLARE @t TABLE (ID INT PRIMARY KEY);
BEGIN TRAN;
UPDATE BatchReports SET
IsProcessed = 1
OUTPUT inserted.ID INTO @t(ID)
WHERE BatchReports.BatchReportGUID = @someGuid;
SELECT b.*
FROM @t t JOIN BatchReports b ON t.ID = b.ID;
COMMIT;