4
8

More than 5 years have passed since last update.

SQLServerで特定のテーブルの変更履歴全部残したいときの備忘録

Posted at

はじめに

とあるシステムでこんな操作すると変な動きするんだけど。。。
問い合わせが来て、調べてみるとデータ不整合。
でもなんでこうなるかわからなくて対象のテーブルの変更履歴を全部残して調査した時のメモ

流れをざっくりと

①対象のテーブルの履歴を残すテーブルを作る。
 →「テーブル名+History」という名前で作成

②対象のテーブルにトリガを作成
 →INSERT、UPDATE、DELETEそれぞれを契機に履歴テーブルにレコードをINSERT

③変な動きをするデータをひたすら追う!

実際にやること

何も考えずに1と2のSQLを流すだけでとりあえずは履歴取れるようになります。

1.履歴を残すストアドを作成

以下のSQLを流して履歴を残す設定をするストアドを作成します。
中身は履歴を残したいテーブル名を引数に渡すと履歴テーブルを作ってトリガを設定するものです。
詳しいことは後述します。

履歴残すストアド作る.sql
--履歴テーブルを作って履歴を残す対象テーブルにトリガを作成するストアド
CREATE PROCEDURE sp_CreateHistoryTable
    @TargetTable NVARCHAR(30)
AS
BEGIN

--履歴テーブル名(履歴を残したいテーブル名+History)
DECLARE @HistoryTable NVARCHAR(34) = @TargetTable + 'History'

--履歴テーブルを作成(対象テーブルのカラム構造のみコピー)
exec('SELECT * INTO ' + @HistoryTable + ' FROM ' + @TargetTable + ' WHERE 1 = 0')

--履歴テーブルにカラム追加
exec('ALTER TABLE ' + @HistoryTable + ' ADD process_datetime datetime, process_action nvarchar(6), process_order tinyint')


--対象テーブルにトリガを作成

--INSERTのトリガ作成
 exec ('
CREATE TRIGGER AddInsertHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER INSERT
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''INSERT'';

    INSERT INTO ' + @HistoryTable + '
    select *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,NULL AS process_order from inserted

END
')
--UPDATEのトリガ作成
exec('
CREATE TRIGGER AddUpdateHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER UPDATE
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''UPDATE'';

    INSERT INTO ' + @HistoryTable + '

        SELECT *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,1 AS process_order FROM deleted
        UNION ALL
        SELECT *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,2 AS process_order FROM inserted
        ORDER BY process_order;

END
')
--DELETEのトリガを作成
exec('
CREATE TRIGGER AddDeleteHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER DELETE
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''DELETE'';

    INSERT INTO ' + @HistoryTable + '
    select *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,NULL AS process_order from deleted

END
')

END;

2.履歴を残すストアドを実行

上で作ったストアドに履歴を取りたいテーブル名を渡して履歴を残す設定をします。

履歴残す.sql
EXEC dbo.sp_CreateHistoryTable @TargetTable = 'Table1'
EXEC dbo.sp_CreateHistoryTable @TargetTable = 'Table2'

やったことの中身詳しく

(上に書いてることを分割しているだけです。)

1.履歴を貯めるテーブルを作る

履歴を残したい対象のテーブル構造のみコピー。
「WHERE 1 = 0」を消すとデータも丸ごとコピーできるSQL。便利。

履歴を貯めるテーブルを作成.sql
SELECT * INTO @HistoryTable FROM @TargetTable WHERE 1 = 0

2.履歴を貯めるテーブルに必要なカラムを追加する。

ここではprocess_datetime、process_action、process_orderの3つを追加。

①process_datetime → 更新日時
②process_action → INSERT or UPDATE or DELETE
③process_order → UPDATEの場合、更新前'1'、更新後'2'、INSERT、DELETEはnull

履歴を貯めるテーブルにカラム追加.sql
ALTER TABLE @HistoryTable ADD process_datetime datetime, process_action nvarchar(6), process_order tinyint

対象のテーブルにトリガを作成

INSERT,UPDATE,DELETEそれぞれとトリガにして履歴テーブルにデータを登録するようにします。

--INSERTのトリガ作成
 exec ('
CREATE TRIGGER AddInsertHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER INSERT
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''INSERT'';

    INSERT INTO ' + @HistoryTable + '
    select *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,NULL AS process_order from inserted

END
')
--UPDATEのトリガ作成
exec('
CREATE TRIGGER AddUpdateHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER UPDATE
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''UPDATE'';

    INSERT INTO ' + @HistoryTable + '

        SELECT *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,1 AS process_order FROM deleted
        UNION ALL
        SELECT *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,2 AS process_order FROM inserted
        ORDER BY process_order;

END
')
--DELETEのトリガを作成
exec('
CREATE TRIGGER AddDeleteHistory_' + @TargetTable + '
ON ' + @TargetTable + '
AFTER DELETE
AS
BEGIN

    DECLARE @ProcessDateTime DATETIME = GETDATE(),
            @ProcessAction   NVARCHAR(20) =''DELETE'';

    INSERT INTO ' + @HistoryTable + '
    select *,@ProcessDateTime AS process_datetime,@ProcessAction AS process_action ,NULL AS process_order from deleted

END
')

おわりに

アプリにデバックログを仕込んで様子を見る方法もありますが、
漏れる可能性もあるので、対象のテーブルが分かっている場合は、
全量データを取るのが良いのかなと思います。
(終わったらトリガと履歴テーブルを削除をしないとデータ量がとんでもないことに。。。)

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8