6

最近のプロジェクトの1つでは、フィールド変更の追跡を実装する必要がありました。したがって、ユーザーがフィールドの値を変更するたびに、変更を完全に監査できるようにするために変更が記録されました。

FieldChangesデータベースでは、これを次のフィールドを持つ単一のテーブルとして実装しました。

  • TableName
  • フィールド名
  • RecordId
  • DateOfChange
  • ChangedBy
  • IntValue
  • TextValue
  • DateTimeValue
  • BoolValue

オブジェクトへの変更を保存するsprocは、フィールドごとに変更されているかどうかを判断し、変更されている場合はFieldChangesにレコードを挿入します。変更されたフィールドのタイプがである場合は、テーブルのフィールドにint記録します。IntValueFieldChanges

これは、任意のID値を持つ任意のテーブルの任意のフィールドについて、FieldChangesテーブルにクエリを実行して変更のリストを取得できることを意味します。

これは非常にうまく機能しますが、少し不器用です。同様の機能を実装した他の誰かがより良いアプローチを提案できますか、そしてなぜ彼らはそれがより良いと思うのですか?

私は本当に興味があるでしょう-ありがとう。

デビッド

4

4 に答える 4

6

トリガー。

監査ログ トリガーを簡単に作成/管理できるように、GUI (社内ではRed Matrix Reloadedと呼ばれます) を作成しました。

使用されているもののDDLは次のとおりです。


監査ログ テーブル

CREATE TABLE [AuditLog] (
    [AuditLogID] [int] IDENTITY (1, 1) NOT NULL ,
    [ChangeDate] [datetime] NOT NULL CONSTRAINT [DF_AuditLog_ChangeDate] DEFAULT (getdate()),
    [RowGUID] [uniqueidentifier] NOT NULL ,
    [ChangeType] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [TableName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [FieldName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [OldValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [NewValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [Username] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [Hostname] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [AppName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [UserGUID] [uniqueidentifier] NULL ,
    [TagGUID] [uniqueidentifier] NULL ,
    [Tag] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL 
)

挿入をログに記録するトリガー

CREATE TRIGGER LogInsert_Nodes ON dbo.Nodes
FOR INSERT
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /*We dont' log individual field changes Old/New because the row is new.
    So we only have one record - INSERTED*/

    INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)

    SELECT
        getdate(), --ChangeDate
        i.NodeGUID, --RowGUID
        'INSERTED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        '', --FieldName
        i.ParentNodeGUID, --TagGUID
        i.Caption, --Tag
        null, --OldValue
        null --NewValue
    FROM Inserted i

更新をログに記録するトリガー

CREATE TRIGGER LogUpdate_Nodes ON dbo.Nodes
FOR UPDATE AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /* ParentNodeGUID uniqueidentifier */
    IF UPDATE (ParentNodeGUID)
    BEGIN
        INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)
        SELECT 
            getdate(), --ChangeDate
            i.NodeGUID, --RowGUID
            'UPDATED', --ChangeType
            USER_NAME(), HOST_NAME(), APP_NAME(), 
            @SavedUserGUID, --UserGUID
            'Nodes', --TableName
            'ParentNodeGUID', --FieldName
            i.ParentNodeGUID, --TagGUID
            i.Caption, --Tag
            d.ParentNodeGUID, --OldValue
            i.ParentNodeGUID --NewValue
        FROM Inserted i
            INNER JOIN Deleted d
            ON i.NodeGUID = d.NodeGUID
        WHERE (d.ParentNodeGUID IS NULL AND i.ParentNodeGUID IS NOT NULL)
        OR (d.ParentNodeGUID IS NOT NULL AND i.ParentNodeGUID IS NULL)
        OR (d.ParentNodeGUID <> i.ParentNodeGUID)
    END

    /* Caption varchar(255) */
    IF UPDATE (Caption)
    BEGIN
        INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)
        SELECT 
            getdate(), --ChangeDate
            i.NodeGUID, --RowGUID
            'UPDATED', --ChangeType
            USER_NAME(), HOST_NAME(), APP_NAME(), 
            @SavedUserGUID, --UserGUID
            'Nodes', --TableName
            'Caption', --FieldName
            i.ParentNodeGUID, --TagGUID
            i.Caption, --Tag
            d.Caption, --OldValue
            i.Caption --NewValue
        FROM Inserted i
            INNER JOIN Deleted d
            ON i.NodeGUID = d.NodeGUID
        WHERE (d.Caption IS NULL AND i.Caption IS NOT NULL)
        OR (d.Caption IS NOT NULL AND i.Caption IS NULL)
        OR (d.Caption <> i.Caption)
    END

...

/* ImageGUID uniqueidentifier */
IF UPDATE (ImageGUID)
BEGIN
    INSERT INTO AuditLog(
        ChangeDate, RowGUID, ChangeType, 
        Username, HostName, AppName,
        UserGUID, 
        TableName, FieldName, 
        TagGUID, Tag, 
        OldValue, NewValue)
    SELECT 
        getdate(), --ChangeDate
        i.NodeGUID, --RowGUID
        'UPDATED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        'ImageGUID', --FieldName
        i.ParentNodeGUID, --TagGUID
        i.Caption, --Tag
        (SELECT Caption FROM Nodes WHERE NodeGUID = d.ImageGUID), --OldValue
        (SELECT Caption FROM Nodes WHERE NodeGUID = i.ImageGUID) --New Value
    FROM Inserted i
        INNER JOIN Deleted d
        ON i.NodeGUID = d.NodeGUID
    WHERE (d.ImageGUID IS NULL AND i.ImageGUID IS NOT NULL)
    OR (d.ImageGUID IS NOT NULL AND i.ImageGUID IS NULL)
    OR (d.ImageGUID <> i.ImageGUID)
END

ログを記録するトリガー 削除

CREATE TRIGGER LogDelete_Nodes ON dbo.Nodes
FOR DELETE
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /*We dont' log individual field changes Old/New because the row is new.
    So we only have one record - DELETED*/

    INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue,NewValue)

    SELECT
        getdate(), --ChangeDate
        d.NodeGUID, --RowGUID
        'DELETED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        '', --FieldName
        d.ParentNodeGUID, --TagGUID
        d.Caption, --Tag
        null, --OldValue
        null --NewValue
    FROM Deleted d

また、ソフトウェアのどのユーザーが更新を行ったかを知るために、ストアド プロシージャを呼び出して、すべての接続が "SQL Server にログオン" します。

CREATE PROCEDURE dbo.SaveContextUserGUID @UserGUID uniqueidentifier AS

/* Saves the given UserGUID as the session's "Context Information" */
IF @UserGUID IS NULL
BEGIN
    PRINT 'Emptying CONTEXT_INFO because of null @UserGUID'
    DECLARE @BinVar varbinary(128)
    SET @BinVar = CAST( REPLICATE( 0x00, 128 ) AS varbinary(128) )
    SET CONTEXT_INFO @BinVar
    RETURN 0
END

DECLARE @UserGUIDBinary binary(16) --a guid is 16 bytes
SELECT @UserGUIDBinary = CAST(@UserGUID as binary(16))
SET CONTEXT_INFO @UserGUIDBinary


/* To load the guid back 
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

select @SavedUserGUID AS UserGUID
*/

ノート

  • Stackoverflow コード形式ではほとんどの空白行が削除されるため、書式設定がうまくいきません
  • 統合セキュリティではなく、ユーザーのテーブルを使用します
  • このコードは利便性のために提供されています。デザインの選択に対する批判は許可されていません。純粋主義者は、すべてのロギング コードはビジネス レイヤーで実行する必要があると主張する場合があります。
  • SQL Server のトリガーを使用して BLOB をログに記録することはできません (ブログの "前" のバージョンはありません。あるものだけがあります)。Text と nText は blob です。これにより、メモがログに記録されなくなるか、varchar(2000) になります。
  • タグ列は、行を識別するための任意のテキストとして使用されます (たとえば、顧客が削除された場合、タグは監査ログ テーブルに「General Motors North America」と表示されます。
  • TagGUID は、行の「親」を指すために使用されます。たとえば、InvoiceLineItems のログInvoiceHeaderを指します。このようにして、特定の請求書に関連する監査ログ エントリを検索する人は、監査証跡の明細項目の TagGUID によって、削除された「明細項目」を見つけることができます。
  • 意味のある文字列を取得するために、「OldValue」と「NewValue」の値が副選択として書き込まれることがあります。つまり」

    OldValue: {233d-ad34234..} NewValue: {883-sdf34...}

監査証跡では、次のものよりも役に立ちません。

OldValue: Daimler Chrysler
NewValue: Cerberus Capital Management

最後の注意: 私たちがしていることを気にしないでください。これは私たちにとっては素晴らしいことですが、他の誰もが自由に使用しないことができます。

于 2011-06-09T19:11:13.460 に答える
1

このためのエンタープライズ パターンは、すべての列の変更後イメージ (場合によっては変更前イメージ) を表示するために、作成するすべてのテーブルに修正シャドー テーブルを用意することです。必要になるだろう:

  • 修正表を作成するスクリプト
  • それらを移入するトリガー
  • 時間の経過とともにテーブルが変更された場合は、上記を維持します。

しかし、適切に設定された企業の場合、これらすべてがすでに整っているはずです。

私の組織では、これを次の目的でのみ使用しています。

  • データベース管理者の監査と、(SQL を使用して) 何が起こったのかを手動で判断するためのサポート。
  • エンタープライズ データ ウェアハウス (SAS) は、分析のために本番システムからすべてのデルタを吸い込みます。

運用システム自体に必要な場合は、別のテーブルを作成します。

于 2010-04-23T11:59:13.673 に答える
0

バージョン管理によってこれを解決します。1つのバージョン-1つのテーブル行。最新バージョン-最終更新日が最大の行。

于 2010-04-21T12:28:56.230 に答える
0

トリガーを作成し、トリガーが変更を自動的に追跡して監査テーブルに記録するようにします。

于 2010-04-28T21:02:27.647 に答える