4

UPDATE が呼び出されたときに特定のテーブルに加えられた変更の履歴を保持する必要がありますが、特定の列のみを気にします。

したがって、履歴テーブルを作成しました。

CREATE TABLE [dbo].[SourceTable_History](
    [SourceTable_HistoryID] [int] IDENTITY(1,1) NOT NULL,
    [SourceTableID] [int] NOT NULL,
    [EventDate] [date] NOT NULL,
    [EventUser] [date] NOT NULL,
    [ChangedColumn] VARCHAR(50) NOT NULL,
    [PreviousValue] VARCHAR(100) NULL,
    [NewValue] VARCHAR(100) NULL

    CONSTRAINT pk_SourceTable_History PRIMARY KEY ([SourceTable_HistoryID]),
    CONSTRAINT fk_SourceTable_HistoryID_History_Source FOREIGN KEY ([SourceTableID]) REFERENCES SourceTable (SourceTableId)
)

私の計画は、SourceTable に Update トリガーを作成することです。ビジネスは特定の列の変更のみを気にするので、疑似コードでは、次のようなことを計画していました

If source.Start <> new.start
Insert into history (PrimaryKey, EventDate, EventUser, ColumnName, OldValue, NewValue)
(PK, GETDATYE(), updateuser, "StartDate", old.value, new.value)

そして、履歴が必要な列ごとにそのようなブロックがあります。

CDC を使用することは許可されていないため、独自に展開する必要があり、これが今のところ私の計画です。

これは適切な計画だと思いますか?

監視する必要があるテーブルは 7 つあり、列数はテーブルごとに 2 ~ 5 列です。

特定の列の前後の値を最初に比較してから新しい行を書き込むトリガーを取得する方法を考え出す必要があります。

私はそれが次のような単純なものだと思った:

CREATE TRIGGER tr_PersonInCareSupportNeeds_History
ON PersonInCareSupportNeeds
FOR UPDATE
AS
BEGIN
    IF(inserted.StartDate <> deleted.StartDate)
    BEGIN
        INSERT INTO [dbo].[PersonInCareSupportNeeds_History]
        ([PersonInCareSupportNeedsID], [EventDate], [EventUser], [ChangedColumn], [PreviousValue], [NewValue])
        VALUES
        (inserted.[PersonInCareSupportNeedsID], GETDATE(), [LastUpdateUser], 'StartDate', deleted.[StartDate], deleted.[StartDate])
    END
END
4

3 に答える 3

2

トリガーベースの監査システムがあります。基本的には、監査トリガーを生成するためのサードパーティ製ツールである ApexSQL Audit がトリガーを作成し、ストレージを管理する方法を分析して作成し、それに基づいて独自のシステムを開発しました。

あなたのソリューションは一般的には問題ないと思いますが、ストレージを少し変更してスケーリングを計画する必要があると思います。

ビジネスがすべてのテーブルのすべての列を追跡することを決定した場合はどうなるでしょうか? 挿入と削除も追跡することにした場合はどうなりますか? あなたのソリューションはこれに対応できますか?

ストレージ: 2 つのテーブルを使用してデータを保持します。トランザクションに関するすべての情報 (いつ、誰が、アプリケーション名、テーブル名、スキーマ名、影響を受ける行など) を保持するための 1 つのテーブル。そして、実際のデータを保持する別のテーブル (前後の値、主キーなど)。

トリガー:最終的に、挿入、更新、および削除トリガーのテンプレートと、テーブルと列を入力してアプリケーションが DDL を出力する非常に単純な C# アプリが完成しました。これにより、多くの時間を節約できました。

于 2013-07-26T09:50:27.920 に答える
1

要件に応じて、履歴テーブルは、キャプチャするテーブルと追加の監査の詳細 (誰が、いつ、なぜ) を反映する必要があると思います。

これにより、同じ既存のロジック (SQL、データ クラス、画面など) を使用して履歴データを表示することが容易になります。

あなたの設計でデータを取り込むことは問題ありませんが、使用可能な形式でデータを引き出すのはどれほど簡単でしょうか?

于 2013-07-26T04:07:39.793 に答える
1

まあ、あなたの考えは悪くないと思います。実際、私は本番環境で同様のシステムを持っています。完全なコード (非同期履歴保存付き) は提供しませんが、いくつかのガイドラインを提供できます。

主なアイデアは、データをリレーショナル モデルからEntity-Attribute-Value モデルに変換することです。また、トリガーをできるだけ一般的なものにしたいと考えています。つまり、列名を明示的に記述しないでください。これはさまざまな方法で実行できますが、SQL Server で私が知っている最も一般的な方法は、FOR XMLを使用してから xml から選択することです。

declare @Data xml

select @Data = (select * from Test for xml raw('Data'))

select
    T.C.value('../@ID', 'bigint') as ID,
    T.C.value('local-name(.)', 'nvarchar(128)') as Name,
    T.C.value('.', 'nvarchar(max)') as Value
from @Data.nodes('Data/@*') as T(C)

SQL フィドルの例

2 つのテーブルの異なる行を取得するには、 EXCEPTを使用できます。

select * from Test1 except select * from Test2
union all
select * from Test2 except select * from Test1

SQL フィドルの例

最後に、トリガーは次のようになります。

create trigger utr_Test_History on Test
after update
as
begin
    declare @Data_Inserted xml, @Data_Deleted xml

    select @Data_Inserted = 
    (
        select * 
        from (select * from inserted except select * from deleted) as a
        for xml raw('Data')
    )

    select @Data_Deleted = 
    (
        select * 
        from (select * from deleted except select * from inserted) as a
        for xml raw('Data')
    )

    ;with CTE_Inserted as (
        select
            T.C.value('../@ID', 'bigint') as ID,
            T.C.value('local-name(.)', 'nvarchar(128)') as Name,
            T.C.value('.', 'nvarchar(max)') as Value
        from @Data_Inserted.nodes('Data/@*') as T(C)    
    ), CTE_Deleted as (
        select
            T.C.value('../@ID', 'bigint') as ID,
            T.C.value('local-name(.)', 'nvarchar(128)') as Name,
            T.C.value('.', 'nvarchar(max)') as Value
        from @Data_Deleted.nodes('Data/@*') as T(C)     
    )
    insert into History (Table_Name, Record_ID, Event_Date, Event_User, Column_Name, Value_Old, Value_New)
    select 'Test', isnull(I.ID, D.ID), getdate(), system_user, isnull(D.Name, I.Name), D.Value, I.Value
    from CTE_Inserted as I
        full outer join CTE_Deleted as D on D.ID = I.ID and D.Name = I.Name
    where
    not
    (
        I.Value is null and D.Value is null or
        I.Value is not null and D.Value is not null and I.Value = D.Value
    )
end

SQL フィドルの例

于 2013-07-26T05:00:52.453 に答える