SQL Server 2005データベースの変更の追跡
-
08-07-2019 - |
質問
データベースへの変更を追跡するソリューションの開発を担当しました。
更新の場合、キャプチャする必要があります:
- 更新日
- 古い値
- 新しい値
- 影響を受けるフィールド
- 変更を行う人
- レコードID
- テーブルレコードは にあります
削除の場合:
- 削除日
- 削除を行う人
- 削除されたレコードのタイトル/説明/ ID。すべての変更を追跡しているテーブルには、タイトルまたは説明フィールドがあります。レコードが削除される前にこれをキャプチャしたいです。
- テーブルレコードがありました
挿入の場合:
- 挿入日
- 変更を行う人
- レコードID
- テーブルレコードは にあります
これを行うにはいくつかの方法を考えました:
- 更新/削除/挿入にストアドプロシージャを使用しています。一般的な<!> quot; tracking <!> quot;を作成します。表。すべてのデータをキャプチャするのに十分なフィールドがあります。次に、各ストアドプロシージャに<!> quot; Insert record into tracking table <!> quot;の効果に別の行を追加します。
- 欠点:すべての更新/削除/挿入はすべて同じテーブルに寄せ集められます
- 多くのNULLフィールド
- バッチの更新/削除/挿入を追跡するにはどうすればよいですか? <!> lt; ----これは問題ではないかもしれません。アプリケーションでは、このようなことは実際にはしていません。
- 更新を行っているユーザーをキャプチャするにはどうすればよいですか。データベースには1つのアカウントが表示されます。
- 既存の多くのコードを編集して編集します。
- 最後に、更新/削除/挿入後に呼び出されるトリガーを作成できました。最初のソリューションと同じマイナス面の多くは、次の点を除きます。同じくらいのコードを編集する必要があります。更新を追跡する方法がわかりません。トリガーを使用して最近更新されたレコードを表示する方法があるようには見えません。
asp.net、C#、SQL Server 2005、iis6、windows 2003を使用しています。予算がないため、残念ながらこれを支援するものを購入できません。
ご回答ありがとうございます。
解決
トリガーには、さまざまな理由で必要なすべての情報が含まれているわけではありません-しかし、ユーザーIDがクリンチャーではありません。
変更が行われた場所に挿入する共通のspを使用して、あなたは正しい軌道に乗っていると思います。インターフェイスのspを標準化する場合、ゲームの先を行くことになります-追跡されない変更に忍び込むのは難しいでしょう。
これを、会計アプリケーションの監査証跡に相当するものと考えてください。これはジャーナルです。すべてのトランザクションが記録された単一のテーブルです。入金、引き出し、調整などに別々のジャーナルを実装することはありません。これは同じ原則です。
他のヒント
この問題を回避することは嫌いです。予算がないことはわかっていますが、最も簡単な解決策はSQL Server 2008にアップグレードすることです。この機能は組み込み。この質問に出くわした人は、たとえ自分で使用できないとしても、少なくとも言及すべきだと思いました。
(SQL 2008の展開可能なエディションのうち、この機能はエンタープライズでのみ使用可能です。)
すべてのテーブルで2列を使用することをお勧めします。名前は rowhistory および IsDeleted で、データタイプはxmlおよびbitです。 行を削除しないで、常にIsDeletedフラグを使用します 次に、更新トリガーを使用します。同じ例を示します このページというテーブルがあります
CREATE TABLE te_Page([Id] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](200) NOT NULL, [Description] [varchar](200) NULL,[CreatedBy] [uniqueidentifier] NULL, [CreatedDate] [datetime] NOT NULL, [UpdatedBy] [uniqueidentifier] NULL, [UpdatedDate] [datetime] NULL, [IsDeleted] [bit] NULL, [RowHistory] [xml] NULL, CONSTRAINT [PK_tm_Page] PRIMARY KEY CLUSTERED ([Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
テーブルを作成したら、次のコードをコピーアンドペーストするだけで、ページテーブルのタスクが完了します。古い値と新しい値とともに更新される同じ行の行の履歴の記録を開始します。
ALTER Trigger [dbo].[Trg_Te_Page]
On [dbo].[te_Page]
After Update
As
--If @@rowcount = 0 Or Update(RowHistory)
--Return
Declare @xml NVARCHAR(MAX)
Declare @currentxml NVARCHAR(MAX)
Declare @node NVARCHAR(MAX)
Declare @ishistoryexists XML
Declare @FormLineAttributeValueId int
-- new Values
Declare @new_Name varchar(200)
Declare @new_Description varchar(200)
Declare @new_CreatedBy UNIQUEIDENTIFIER
Declare @new_CreatedDate DATETIME
Declare @new_UpdatedBy UNIQUEIDENTIFIER
Declare @new_UpdatedDate DATETIME
Declare @new_IsDeleted BIT
--old values
Declare @old_Name varchar(200)
Declare @old_Description varchar(200)
Declare @old_CreatedBy UNIQUEIDENTIFIER
Declare @old_CreatedDate DATETIME
Declare @old_UpdatedBy UNIQUEIDENTIFIER
Declare @old_UpdatedDate DATETIME
Declare @old_IsDeleted BIT
-- declare temp fmId
Declare @fmId int
-- declare cursor
DECLARE curFormId cursor
FOR select Id from INSERTED
-- open cursor
OPEN curFormId
-- fetch row
FETCH NEXT FROM curFormId INTO @fmId
WHILE @@FETCH_STATUS = 0
BEGIN
Select
@FormLineAttributeValueId = Id,
@old_Name = Name,
@old_Description = [Description],
@old_CreatedBy = CreatedBy,
@old_CreatedDate =CreatedDate,
@old_UpdatedBy =UpdatedBy,
@old_UpdatedDate =UpdatedDate,
@old_IsDeleted = IsDeleted,
@currentxml = cast(RowHistory as NVARCHAR(MAX))
From DELETED where Id=@fmId
Select
@new_Name = Name,
@new_Description = [Description],
@new_CreatedBy = CreatedBy,
@new_CreatedDate =CreatedDate,
@new_UpdatedBy =UpdatedBy,
@new_UpdatedDate =UpdatedDate,
@new_IsDeleted = IsDeleted
From INSERTED where Id=@fmId
set @old_Name = Replace(@old_Name,'&','&')
set @old_Name = Replace(@old_Name,'>','>')
set @old_Name = Replace(@old_Name,'<','<')
set @old_Name = Replace(@old_Name,'"','"')
set @old_Name = Replace(@old_Name,'''',''')
set @new_Name = Replace(@new_Name,'&','&')
set @new_Name = Replace(@new_Name,'>','>')
set @new_Name = Replace(@new_Name,'<','<')
set @new_Name = Replace(@new_Name,'"','"')
set @new_Name = Replace(@new_Name,'''',''')
set @old_Description = Replace(@old_Description,'&','&')
set @old_Description = Replace(@old_Description,'>','>')
set @old_Description = Replace(@old_Description,'<','<')
set @old_Description = Replace(@old_Description,'"','"')
set @old_Description = Replace(@old_Description,'''',''')
set @new_Description = Replace(@new_Description,'&','&')
set @new_Description = Replace(@new_Description,'>','>')
set @new_Description = Replace(@new_Description,'<','<')
set @new_Description = Replace(@new_Description,'"','"')
set @new_Description = Replace(@new_Description,'''',''')
set @xml = ''
BEGIN
-- for Name
If ltrim(rtrim(IsNull(@new_Name,''))) != ltrim(rtrim(IsNull(@old_Name,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Name" OldValue="'+ @old_Name + '" NewValue="' + @new_Name + '"/>'
-- for Description
If ltrim(rtrim(IsNull(@new_Description,''))) != ltrim(rtrim(IsNull(@old_Description,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Description" OldValue="'+ @old_Description + '" NewValue="' + @new_Description + '"/>'
-- CreatedDate
If IsNull(@new_CreatedDate,'') != IsNull(@old_CreatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="CreatedDate" OldValue="'+ cast(isnull(@old_CreatedDate,'') as varchar(100)) + '" NewValue="' + cast(isnull(@new_CreatedDate,'') as varchar(100)) + '"/>'
-- CreatedBy
If cast(IsNull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar (36)) != cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="CreatedBy" OldValue="'+ cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(isnull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- UpdatedDate
If IsNull(@new_UpdatedDate,'') != IsNull(@old_UpdatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedDate" OldValue="'+ cast(IsNull(@old_UpdatedDate,'') as varchar(100)) + '" NewValue="' + cast(IsNull(@new_UpdatedDate,'') as varchar(100)) + '"/>'
-- UpdatedBy
If cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) != cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedBy" OldValue="'+ cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- IsDeleted
If cast(IsNull(@new_IsDeleted,'') as varchar(10)) != cast(IsNull(@old_IsDeleted,'') as varchar(10))
set @xml = @xml + '<ColumnInfo ColumnName="IsDeleted" OldValue="'+ cast(IsNull(@old_IsDeleted,'') as varchar(10)) + '" NewValue="' + cast(IsNull(@new_IsDeleted,'') as varchar(10)) + '" />'
END
Set @xml = '<RowInfo TableName="te_Page" UpdatedBy="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(50)) + '" UpdatedDate="' + Convert(Varchar(20),GetDate()) + '">' + @xml + '</RowInfo>'
Select @ishistoryexists = RowHistory From DELETED
--print @ishistoryexists
If @ishistoryexists is null
Begin
Set @xml = '<History>' + @xml + '</History>'
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
Else
Begin
set @xml = REPLACE(@currentxml, '<History>', '<History>' + @xml)
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
FETCH NEXT FROM curFormId INTO @fmId
END
CLOSE curFormId
DEALLOCATE curFormId
更新を実行するたびに、データは rowhistory 列に保存されます
これが処理されるのを見てきた1つの方法(正直なところ、お勧めしません)は、ストアドプロシージャを介して処理し、userid / username / whateverをパラメーターとして渡します。ストアドプロシージャは、関連する詳細を中央ログテーブルに書き込むロギングプロシージャを呼び出します。
しかし、ここで少しおかしくなりました...
INSERT / UPDATEの場合、INSERT / UPDATEが正常に完了すると、関連する行がXMLデータとしてテーブルに保存されました。 DELETEの場合、行はDELETEの実行前に保存されていました(実際には、少なくともSQL Server 2005ではDELETEステートメントの出力から取得できたはずです)。
正しく覚えていれば、テーブルには、UserID、ロギングのDateTime、トランザクションタイプ(I / U / D)、関連する行を含むXMLデータ、テーブル名、および主キー値(主に使用される)必要なレコードをすばやく検索できます)。
ただし、猫の皮を剥ぐ多くの方法...
維持することは簡単です。必要に応じて後で展開します。
実行できる場合は、ストアドプロシージャを介してテーブルでアクション可能なステートメントのみを実行できるようにユーザーをロックダウンし、そこから(必要に応じて)ロギングを処理します。
独自のビルドを行い、ユーザーとpcが各追加/更新ストアドプロシージャに渡されることを必要としました。元のレコードを取得して変数を設定し、渡された変数と比較してテーブルにデータを記録するだけです。 削除の場合、元のテーブルのコピー+タイムスタンプフィールドがあるだけなので、レコードは実際には削除されず、必要なときにいつでも復元できます(明らかに、FK関係などの削除ルーチンがチェックします)。
ログテーブルの追加/更新は次のようになります 日付時刻、 table_name、 column_name、 record_id、 old_value、 new_value、 ユーザーID、 コンピューター
nullは挿入しないため、空の文字列に変換します。新しいエントリは、old_value列で「{new entry}」でマークされます。 record_idは、その単一レコードを一意に識別するためのキー列で構成されます(field1 + '。' + field2 + ...)
まず、すべてのテーブルで、少なくともこれらの列をデータ列DateCreated、UserCreated、DateModified、UserModifiedに追加する必要があります。おそらく、あなたは<!> quot; Status <!> quot;を追加したいと思うかもしれません。または<!> quot; LastAction <!> quot;列を削除して、行を実際に削除しないように、削除/挿入/更新ステータスに設定します。次に、<!> quot; History table <!> quot;を作成できます。これは最初のテーブルの正確なコピーです。次に、更新または削除時に、トリガーが削除済みテーブルエントリを履歴テーブルにコピーし、DateModified、UserModified、およびStatusフィールドを同時に変更します。
SQL Serverで、ビューを使用してデータにアクセスし、INSTEAD OFトリガーを使用して挿入、更新、削除を処理するセットアップを行いました。
たとえば、ビューの INSTEAD OF DELETE トリガーは、基になるテーブルのレコードを削除済みとしてマークし、削除されたレコードを表示しないようにビューがフィルターされました。
すべてのトリガーで、変更日とユーザー名を更新しました。問題は、データベースのユーザー名を記録することです。これは、最終的なアプリケーションのユーザー名とは異なります。
これを機能させるには、ビューをスキーマバインドする必要があります。
DBを変更するユーザーのロギングについて:DBに必要な数のSQLユーザーを作成できます。また、セッションおよびプログラム/スクリプトへの制限付き/登録済みアクセスを使用する場合 その情報を使用して、異なるDB接続設定(つまり、ユーザー名)を開始できます。 DBによる操作の前。
少なくともPHP指向のスクリプトでは実行可能ですが、asp.netでは間違っている可能性があります。