sql >> データベース >  >> RDS >> Sqlserver

トリガーがテーブルにある場合、OUTPUT句でUPDATEを使用することはできません

    視界警告 :他の答えはしないでください。間違った値になります。なぜそれが間違っているのかを読んでください。

    UPDATEを作成するために必要な恨みを考えると OUTPUTを使用 SQL Server 2008 R2で作業している場合、クエリを次のように変更しました:

    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の使用をやめました 。これは、EntityFramework自体ほど悪くはありません。 これとまったく同じハックを使用します!

    うまくいけば2012 2014 2016 2018 2019 2020年にはより良い実装があります。

    更新:OUTPUTの使用は有害です

    私たちが始めた問題は、OUTPUTを使おうとしたことでした。 "after"を取得する句 テーブル内の値:

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

    その後、それはよく知られている制限に達します(「修正されません」 バグ)SQL Serverの場合:

    ステートメントにINTO句のないOUTPUT句が含まれている場合、DMLステートメントのターゲットテーブル'BatchReports'でトリガーを有効にすることはできません

    回避策の試み#1

    そこで、中間の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を挿入することが許可されていないために失敗する場合を除きます テーブルに(一時的なテーブル変数でさえ)。

    回避策#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ステートメントの開始
      • 行を変更します
        • タイムスタンプが更新されます(例:2→3)
      • OUTPUTは新しいタイムスタンプを取得します(つまり3)
      • トリガー実行
        • 行を再度変更します
          • タイムスタンプが更新されます(例:3→4)
    • UPDATEステートメントが完了しました
    • OUTPUTは3を返します (間違った値)

    これは次のことを意味します:

    • UPDATEステートメントの最後にあるタイムスタンプ( 4 )は取得されません。 )
    • 代わりに、UPDATEステートメントの不確定な途中にあったタイムスタンプを取得します( 3
    • 正しいタイムスタンプが取得されません

    同じことがすべてにも当てはまります 任意のを変更するトリガー 行の値。 OUTPUT UPDATEの終了時点で値を出力しません。

    これは、OUTPUTが正しい値を返すことを信頼できないことを意味します。

    この痛ましい現実はBOLに文書化されています:

    OUTPUTから返される列は、INSERT、UPDATE、またはDELETEステートメントが完了した後、トリガーが実行される前のデータをそのまま反映します。

    Entity Frameworkはどのように解決しましたか?

    .NET Entity Frameworkは、楽観的同時実行性のために行バージョンを使用します。 EFは、timestampの値を知ることに依存します UPDATEを発行した後に存在するため。

    OUTPUTは使用できないため 重要なデータについては、MicrosoftのEntityFrameworkは私が行うのと同じ回避策を使用します。

    回避策#3-最終-OUTPUT句を使用しないでください

    を取得するには 値、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は使用しないでください 。

    はい、競合状態に悩まされていますが、それがSQLServerで可能な最善の方法です。

    INSERTについてはどうですか

    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
    

    ここでも、SELECTを使用します OUTPUT句を信頼するのではなく、行を読み取るステートメント。



    1. 注文後にOracleクエリによって返される行数を制限するにはどうすればよいですか?

    2. SQLServerでテーブル値関数を作成する

    3. 非常に大きなテーブルでの(列ストア)圧縮の楽しみ–パート1

    4. 新しいOracleユーザーを作成して権限を付与する:構文と例