私はUPDATE
クエリでOUTPUT
を実行しています:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
この声明は申し分ありません。トリガーがテーブルで定義されるまで。その後、私のUPDATE
ステートメントはエラーを取得します 4 :
ステートメントにINTO句のないOUTPUT句が含まれている場合、DMLステートメントのターゲットテーブル 'BatchReports'に有効なトリガーを含めることはできません
現在、この問題は SQL Serverチームによるブログ投稿 で説明されています。
エラーメッセージは一目瞭然です
また、ソリューションも提供します。
INTO句を使用するようにアプリケーションが変更されました
ただし、ブログ投稿全体の頭や尻尾を作ることはできません。
それで、私の質問を聞かせてください:UPDATE
を何に変更すればうまくいきますか?
可視性警告: 最高得票数 を使用しないでください。間違った値が表示されます。それが間違っている方法を読んでください。
SQL Server 2008 R2でUPDATE
を使用するOUTPUT
を機能させるために必要な手間を考えると、クエリを次から変更しました。
_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
の使用を停止しました。 Entity Framework自体がこのまったく同じハックを使用しているので、これはそれほど悪くありません!
うまくいけば 201220142016年 2018年の実装は改善されます。
私たちが始めた問題は、OUTPUT
句を使用して、テーブル内の "after"値を取得しようとしていました。
_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バイト)符号なし整数であることを密かに知っています。 timestamp
ではなくbinary(8)
を使用するように一時テーブル定義を変更できます。
_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ステートメントの終了時の値notであるためです。
これの意味は:
同じことは、any行内のany値を変更するトリガーにも当てはまります。 OUTPUTは、UPDATEの終了時点で値を出力しません。
これは、OUTPUTが正しい値を返すことを信頼していないことを意味します
この痛みを伴う現実はBOLに文書化されています。
OUTPUTから返される列は、INSERT、UPDATE、またはDELETEステートメントが完了した後、トリガーが実行される前のデータを反映しています。
.NET Entity Frameworkは、オプティミスティック同時実行性にrowversionを使用します。 EFは、UPDATEを発行した後、timestamp
の値を知ることに依存しています。
重要なデータにはOUTPUT
を使用できないため、MicrosoftのEntity Frameworkは私と同じ回避策を使用します。
after値を取得するために、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ができる最善の方法です。
Entity Frameworkの機能を実行します。
_SET NOCOUNT ON;
DECLARE @generated_keys table([CustomerID] int)
INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')
SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
INNER JOIN Customers AS t
ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0
_
この制限を回避するには、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
他の回答で注意したように、トリガーがUPDATE
ステートメント自体によって変更された行に、それがOUTPUT
- ingしている列に影響するように書き戻す場合、結果は有用ですが、これはトリガーのサブセットにすぎません。上記の手法は、監査目的で他のテーブルに記録するトリガーや、元の行がトリガーで書き戻された場合でも挿入されたID値を返すなど、他の場合に正常に機能します。
なぜ必要なすべての列をテーブル変数に入れるのですか?主キーが必要なだけで、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;