監査証跡を設定する際に、テーブルのレコードを更新または挿入している人物を追跡することには問題はありませんが、レコードを削除した人物を追跡することはさらに問題が多いようです。
挿入/更新フィールドに「UpdatedBy」を含めることで、挿入/更新を追跡できます。これにより、INSERT/UPDATEトリガーはinserted.UpdatedBy
を介してフィールド "UpdatedBy"にアクセスできます。ただし、削除トリガーを使用すると、データは挿入/更新されません。誰がレコードを削除したかを知ることができるように、情報を削除トリガーに渡す方法はありますか?
これは挿入/更新トリガーです
ALTER TRIGGER [dbo].[trg_MyTable_InsertUpdate]
ON [dbo].[MyTable]
FOR INSERT, UPDATE
AS
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
VALUES (inserted.ID, inserted.LastUpdatedBy)
FROM inserted
SQL Server 2012の使用
誰がレコードを削除したかを知ることができるように、情報を削除トリガーに渡す方法はありますか?
はい:CONTEXT_INFO
と呼ばれるveryクールな(そして十分に活用されていない機能)を使用します。基本的に、すべてのスコープに存在し、トランザクションに拘束されないセッションメモリです。これは、情報(限られたスペースに収まるすべての情報)をトリガーに渡したり、サブプロシージャ/ EXEC呼び出し間でやり取りしたりするために使用できます。そして、私はこれとまったく同じ状況で以前にそれを使用しました。
コンテキスト情報はVARBINARY(128)です
設定: SET CONTEXT_INFO
取得: CONTEXT_INFO()
次のようにテストして、動作を確認します。 CHAR(128)
の前にCONVERT(VARBINARY(128), ..
に変換していることに注意してください。これは、CONTEXT_INFO()
が0x00
sで右パディングされているため、VARBINARY(128)
から取得するときにVARCHAR
に変換しやすくするために、空白埋めを強制するためです。
SELECT CONTEXT_INFO();
-- Initially = NULL
DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
CONVERT(CHAR(128), 'I deleted ALL your records! HA HA!')
);
SET CONTEXT_INFO @EncodedUser;
SELECT CONTEXT_INFO() AS [RawContextInfo],
RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())) AS [DecodedUser];
結果:
0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!
それを一緒に置く:
アプリは、レコードを削除するユーザー名(またはその他)を渡す「削除」ストアドプロシージャを呼び出す必要があります。挿入と更新の操作をすでに追跡しているように思えるため、これはすでに使用されているモデルだと思います。
「削除」ストアード・プロシージャーは次のことを行います。
DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
CONVERT(CHAR(128), @UserName)
);
SET CONTEXT_INFO @EncodedUser;
-- DELETE STUFF HERE
監査トリガーは次のことを行います。
-- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
SELECT del.ID, COALESCE(
LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
'<unknown>')
FROM DELETED del;
@SeanGallardyがコメントで指摘したように、他の手順やアドホッククエリがこのテーブルからレコードを削除しているため、次のいずれかである可能性があることに注意してください:
CONTEXT_INFO
は設定されておらず、まだNULL
です:
このため、上記のINSERT INTO AuditTable
を更新して、COALESCE
を使用して値をデフォルト設定しました。または、デフォルトが不要で名前が必要な場合は、次のようなことを行うことができます。
DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
IF (@UserName IS NULL)
BEGIN
ROLLBACK TRAN; -- cancel the DELETE operation
RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
END;
-- use @UserName in the INSERT...SELECT
CONTEXT_INFO
が有効なユーザー名ではない値に設定されているため、AuditTable.[UserWhoMadeChanges]
フィールドのサイズを超える可能性があります。
このため、LEFT
関数を追加して、CONTEXT_INFO
から取得されたものがINSERT
を壊さないようにしました。コードで述べたように、50
をUserWhoMadeChanges
フィールドの実際のサイズに設定するだけです。
SQL Server 2016以降の更新
SQL Server 2016では、このセッションごとのメモリの改良版であるセッションコンテキストが追加されました。新しいセッションコンテキストは、本質的にはキーと値のペアのハッシュテーブルで、「キー」のタイプはsysname
(つまりNVARCHAR(128)
)で、「値」はSQL_VARIANT
です。意味:
CONTEXT_INFO()
を介して値を取得するときの奇妙な動作を心配する必要がなくなりました(詳細については、私の投稿を参照してください: CONTEXT_INFO()が返されない理由) SET CONTEXT_INFOによって設定された正確な値? )CONTEXT_INFO
の最大128バイトと比較して)詳細については、次のドキュメントページをご覧ください。
アプリケーションレベルではなくSQLサーバーのユーザーIDを記録する場合を除き、この方法は使用できません。
DeletedByという列を用意し、必要に応じて設定することで、ソフト削除を実行できます。その後、更新トリガーは、実際の削除(またはレコードをアーカイブします。通常、ハード削除は可能な限り合法です)を実行し、監査証跡を更新します。 。この方法で削除を強制するには、エラーを発生させるon delete
トリガーを定義します。物理テーブルに列を追加したくない場合は、列を追加するビューを定義し、基本テーブルの更新を処理するinstead of
トリガーを定義できますが、それはやり過ぎかもしれません。
誰がレコードを削除したかを知ることができるように、情報を削除トリガーに渡す方法はありますか?
はい、明らかに2つの方法があります;-)。私が提案したようにCONTEXT_INFO
の使用について予約がある場合 ここに私の他の回答 を他のコード/プロセスから明確に機能的に分離する別の方法を考えました:ローカルを使用する一時テーブル。
一時テーブル名には、同じセッションで実行される可能性のある他のコードと区別するために、削除されるテーブル名を含める必要があります。以下に沿ったもの:#<TableName>DeleteAudit
CONTEXT_INFO
よりもローカル一時テーブルの利点の1つは、別のプロシージャの誰か(つまり、この特定の "削除"プロシージャから何らかの方法で呼び出された場合)が偶然に同じ一時テーブル名を誤って使用すると、サブプロセスが)リクエストされた名前の新しいローカル一時テーブルを作成します(これは同じ名前であっても)この初期一時テーブルとは異なります。b)サブプロセスの新しいローカル一時テーブルに対するDMLステートメントは作成されません。ここで親プロセスに作成されたローカル一時テーブルのデータに影響を与えるため、データの上書きはありません。もちろん、最初に同じ名前のCREATE TABLEを発行せずにサブプロセスがこの一時テーブル名に対してDMLステートメントを発行する場合、それらのDMLステートメントは影響を及ぼしますこのテーブルのデータ。しかし、この時点でreallyエッジケーシーが得られます。これは、CONTEXT_INFO
の重複使用の可能性よりもさらに高くなります(はい、私はそれが起こったことを知っています、それが私が「決して起こらない」ではなく「エッジケース」と言う理由です)。
アプリは、レコードを削除するユーザー名(またはその他)を渡す「削除」ストアドプロシージャを呼び出す必要があります。挿入と更新の操作をすでに追跡しているように思えるため、これはすでに使用されているモデルだと思います。
「削除」ストアード・プロシージャーは次のことを行います。
CREATE TABLE #MyTableDeleteAudit (UserName VARCHAR(50));
INSERT INTO #MyTableDeleteAudit (UserName) VALUES (@UserName);
-- DELETE STUFF HERE
監査トリガーは次のことを行います。
-- Set the datatype and length to be the same as the [UserWhoMadeChanges] field
DECLARE @UserName VARCHAR(50);
IF (OBJECT_ID(N'tempdb..#TriggerTestDeleteAudit') IS NOT NULL)
BEGIN
SELECT @UserName = UserName
FROM #TriggerTestDeleteAudit;
END;
-- catch the following conditions: missing table, no rows in table, or empty row
IF (@UserName IS NULL OR @UserName NOT LIKE '%[a-z]%')
BEGIN
/* -- uncomment if undefined UserName == badness
ROLLBACK TRAN; -- cancel the DELETE operation
RAISERROR('Please set UserName via #TriggerTestDeleteAudit and try again.', 16 ,1);
RETURN; -- exit
*/
/* -- uncomment if undefined UserName gets default value
SET @UserName = '<unknown>';
*/
END;
INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges)
SELECT del.ID, @UserName
FROM DELETED del;
このコードをトリガーでテストしましたが、期待どおりに動作します。