web-dev-qa-db-ja.com

トリガー-動的SQLで挿入/削除されたテーブルの使用

トリガー内で、挿入および削除されたテーブルにあるデータを格納できる一意のテーブル名(NEWID()を使用)を作成しようとしています。

Declare @NewID varchar(50) = Replace(convert(Varchar(50),NEWID()),'-','')
Declare @SQLStr varchar(8000)
Set @SQLStr= 'Select * into [TMPIns' + @newID + '] from inserted'
Exec (@SQLStr)

次のエラーが表示されます:無効なオブジェクト名 '挿入'

私はできることを知っています:

Select * into #inserted from inserted
Set @SQLStr= 'Select * into [TMPIns' + @newID + '] from #inserted'
Exec (@SQLStr)

ただし、これらのテーブルが大きくなる可能性があるため、TempDBを使用したくありません。また、冗長であると感じています。 #insertedの作成を回避する方法はありますか?

6
JohnG

このリクエストの意図された目標に対する洞察がなければ、確かにこの差し迫った問題が解決されたとしても、作業コードは本当に有用なものを提供しないかもしれません。いくつかの懸念があります:

  • このトリガーが配置されるテーブルの数やDML操作の頻度に応じて、このトリガーがテーブルを作成しているデータベースでパフォーマンスの問題が発生する可能性があります。これは、テーブルの作成にスキーマロック(I信じる)そしてそれを頻繁に行うと、他のいくつかの操作が複雑になる可能性があります。
  • このトリガーが複数のテーブルに配置される場合、(動的に作成されたテーブルに独自のプレフィックスを付けない限り)異なるテーブル間の操作をどのように区別しますか?
  • テーブルにUpdatedDateまたは日付フィールドがありますか?そうでない場合は、テーブルの作成日を確認しないと年表がわかりません。
  • これらのさまざまなテーブルをすべてクリーンアップする予定はありますか?これらのテーブルを保持するためだけにスキーマを作成するのが最善でしょうか?
  • 発生したDML操作をどこかに示す予定はありますか?
  • UPDATEの「before」と「after」の値を追跡する場合は、inserteddeletedの両方のテーブルをキャプチャする必要があります。ただし、GUIDベースの名前がある場合、特定のUPDATE操作の「挿入」コピーテーブルと「削除」コピーテーブルを相互に関連付けることはできません。同じGUID=値を再利用し、テーブル名の接頭辞に「挿入」または「削除」を示す必要があります。テーブルを動的に作成していない場合は、 DMLアクションを指定する列、inserteddeletedの両方のテーブルを既存のテーブルにダンプし、GUIDまたはシーケンスのINTを使用して相関させる同じUPDATE操作の2行の間。
  • 使用しているSQL Serverのバージョンとエディションに応じて、 変更追跡 および 変更データキャプチャ を確認することをお勧めします。

ただし、言われていることはすべて、動的SQLを介してinsertedおよびdeletedテーブルを操作する問題は興味深い問題です。残念ながら、T-SQLでは実行できません。だから今はまた挑戦です:-)。幸いなことに、これは実際に行うことができます。どうして?私たちの友人、SQLCLR氏の少しの助けを借りて。

現在、SQLCLRトリガーを実際に要求したり、SQLCLRトリガーから利益を得たりするような状況はそれほど多くないようです。それらは、SQLCLRで作成できる最も有用でないもののようです。ただし、ここでは、これらが最適なシナリオを示します。 SQLCLRコードから送信されたSQLは動的SQLです。また、SQLCLRトリガーはinsertedおよびdeletedテーブルにアクセスできるため、SQLCLRトリガーは動的SQLのinsertedおよびdeletedテーブルにアクセスできるようです。以下は、この要求を実行するために正確に実行するコードです(DB接続はインプロセスの「コンテキスト接続」を使用しているため、アセンブリにPERMISSION_SET = SAFEを付けることができます。非対称キーまたはデータベースをTRUSTWORTHY ON)に設定するには:

トリガーを作成するためのテストテーブル(Visual Studio/SSDTを使用している場合は、テーブル定義をプロジェクトに含める必要があります):

CREATE TABLE TableThatHasTriggers
(
   TableThatHasTriggersID INT IDENTITY(1, 1) NOT NULL
                          CONSTRAINT [PK_TableThatHasTriggers] PRIMARY KEY, 
   InsertTime DATETIME NOT NULL
              CONSTRAINT [DF_TableThatHasTriggers_InsertTime] DEFAULT (GETDATE()),
   SomeValue NVARCHAR(50) COLLATE Latin1_General_100_CI_AS NULL
);

SQLCLR C#コード:

using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;

public class Triggers
{
    [SqlTrigger(Target = "TableThatHasTriggers", Event = "FOR INSERT, UPDATE")]
    public static void tr_TableThatHasTriggers_audit()
    {
        string _AuditSQL = @"
                SELECT ins.*
                INTO   dbo.[TMPIns_" + Guid.NewGuid().ToString().Replace("-", "") + @"]
                FROM   INSERTED ins;
";

        SqlConnection _Connection = new SqlConnection("Context Connection = true");
        SqlCommand _Command = _Connection.CreateCommand();
        _Command.CommandText = _AuditSQL;

        // SqlContext.Pipe.Send(_AuditSQL); // display query for debugging purposes ONLY

        try
        {
            _Connection.Open();
            _Command.ExecuteNonQuery();
        }
        finally
        {
            _Command.Dispose();
            _Connection.Dispose();
        }
    }
}

SQLCLRトリガーをテーブルに配置するT-SQLラッパーオブジェクト:

CREATE TRIGGER [dbo].[tr_TableThatHasTriggers_SQLCLRaudit]
  ON [dbo].[TableThatHasTriggers]
  AFTER INSERT, UPDATE
  AS EXTERNAL NAME
             [InsertedTableViaDynamicSQL].[Triggers].[tr_TableThatHasTriggers_SQLCLRaudit];
1
Solomon Rutzky