リレーショナルデータベースでは、ログトリガーまたは履歴トリガーは、データベーステーブルへの行の挿入、更新、削除などの変更に関する情報を自動的に記録するメカニズムです。
これは、変化データの取得、およびデータウェアハウスにおける緩やかに変化するディメンションの処理のための特定の手法です。
運用データベースは通常、組織の現在の状態を捉えるように設計されており、履歴アーカイブではなく「現在」のスナップショットとして機能します。このような環境では、更新はしばしば破壊的になります。特定のデータポイントが変更されると、システムは効率性を優先し、既存の値を新しい値に置き換えます。たとえば、従業員名簿や顧客名簿で、個人が新しい場所に移動すると、データベースに対して更新操作が実行され、新しい住所が古い住所に直接書き込まれます。その結果、以前の住所は完全に上書きされてシステムから失われ、データベースには最新の情報のみが残り、エンティティの履歴や以前の状態の記録はなくなります。
ログトリガーは、変更を自動的に検知し、情報の以前の状態を保存するための仕組みです。
監査対象とするテーブルがあるとします。このテーブルには以下の列が含まれています。
Column1, Column2, ..., Columnn
これらの列は、以下の型を持つように定義されています。
Type1, Type2, ..., Typen
ログトリガーは、テーブルに対する変更(INSERT、UPDATE、DELETE操作)を、以下のように定義される別の履歴テーブルに書き込むことで機能します。
CREATE TABLE HistoryTable ( Column1 Type1 , Column2 Type2 , : : Columnn Typen ,開始日時(DATETIME) 、終了日時(DATETIME )上記のように、この新しいテーブルには元のテーブルと同じ列に加え、型 と の2つの新しい列が含まれています。これはタプル バージョニングとして知られています。これらの 2 つの追加列は、指定されたエンティティ (主キーのエンティティ) に関連付けられたデータの「有効期間」を定義します。言い換えれば、 (含まれる) と(含まれない)の間の期間にデータがどのように存在していたかを保存します。DATETIMEStartDateEndDateStartDateEndDate
元のテーブル上の各エンティティ(一意の主キー)に対して、履歴テーブルに以下の構造が作成されます。データは例として示されています。

時系列順に表示される場合、任意の行のEndDate列は、その次の行の列(存在する場合)と全く同じであることに注意してください。これは、定義上、の値が含まれないため、両方の行がその時点まで共通していることを意味するものではありません。StartDateEndDate
ログトリガーには、古い値(DELETE、UPDATE)と新しい値(INSERT、UPDATE)がトリガーにどのように公開されるかによって、2つのバリアントがあります(これはRDBMSに依存します)。
レコードデータ構造のフィールドとしての古い値と新しい値
CREATE TRIGGER HistoryTable ON OriginalTable FOR INSERT , DELETE , UPDATE AS DECLARE @ Now DATETIME SET @ Now = GETDATE ()/* セクションを削除中 */UPDATE HistoryTable SET EndDate = @ Now WHERE EndDate IS NULL AND Column1 = OLD . Column1/* セクションの挿入 */INSERT INTO HistoryTable ( Column1 , Column2 , ..., Columnn , StartDate , EndDate ) VALUES ( NEW . Column1 , NEW . Column2 , ..., NEW . Columnn , @ Now , NULL )仮想テーブルの行として表される古い値と新しい値
CREATE TRIGGER HistoryTable ON OriginalTable FOR INSERT , DELETE , UPDATE AS DECLARE @ Now DATETIME SET @ Now = GETDATE ()/* セクションを削除中 */UPDATE HistoryTable SET EndDate = @ Now FROM HistoryTable , DELETED WHERE HistoryTable . Column1 = DELETED . Column1 AND HistoryTable . EndDate IS NULL/* セクションの挿入 */INSERT INTO HistoryTable ( Column1 , Column2 , ..., Columnn , StartDate , EndDate ) SELECT ( Column1 , Column2 , ..., Columnn , @ Now , NULL ) FROM INSERTED上記のコードは、コードの慣用表現として示されています。トリガーの構文は、 RDBMSによって大きく異なります。例えば、次のようになります。
GetDate()はシステムの日時を取得するために使用されますが、特定のRDBMSでは別の関数名を使用したり、別の方法でこの情報を取得したりする場合があります。OLDと と呼ばれていますNEW。特定のRDBMSでは、これらは異なる名前を持つ可能性があります。DELETEDと と呼ばれていますINSERTED。特定のRDBMSでは、これらのテーブルは異なる名前を持つ可能性があります。別のRDBMS(Db2)では、これらの論理テーブルの名前を指定することもできます。BEGINとENDキーワードで囲む必要があります。出典:[ 1 ]
O古い値には、N新しい値にはという名前が付けられています。-- INSERT のトリガーCREATE TRIGGER Database . TableInsert AFTER INSERT ON Database . OriginalTable REFERENCING NEW AS N FOR EACH ROW MODE DB2SQL BEGIN DECLARE Now TIMESTAMP ; SET NOW = CURRENT TIMESTAMP ;INSERT INTO Database.HistoryTable ( Column1 , Column2 , ... , Columnn , StartDate , EndDate ) VALUES ( N.Column1 , N.Column2 , ... , N.Columnn , Now , NULL ) ; END ;-- 削除用のトリガーCREATE TRIGGER Database . TableDelete AFTER DELETE ON Database . OriginalTable REFERENCING OLD AS O FOR EACH ROW MODE DB2SQL BEGIN DECLARE Now TIMESTAMP ; SET NOW = CURRENT TIMESTAMP ;UPDATE Database.HistoryTable SET EndDate = Now WHERE Column1 = O.Column1 AND EndDate IS NULL ; END ;-- 更新用のトリガーCREATE TRIGGER Database . TableUpdate AFTER UPDATE ON Database . OriginalTable REFERENCING NEW AS N OLD AS O FOR EACH ROW MODE DB2SQL BEGIN DECLARE Now TIMESTAMP ; SET NOW = CURRENT TIMESTAMP ;UPDATE Database.HistoryTable SET EndDate = Now WHERE Column1 = O.Column1 AND EndDate IS NULL ;INSERT INTO Database.HistoryTable ( Column1 , Column2 , ... , Columnn , StartDate , EndDate ) VALUES ( N.Column1 , N.Column2 , ... , N.Columnn , Now , NULL ) ; END ;出典:[ 2 ]
DELETED古い値と新しい値は、およびという名前の仮想テーブルの行として表されますINSERTED。CREATE TRIGGER TableTrigger ON OriginalTable FOR DELETE , INSERT , UPDATE ASDECLARE @NOW DATETIME SET @NOW = CURRENT_TIMESTAMPUPDATE HistoryTable SET EndDate = @ now FROM HistoryTable , DELETED WHERE HistoryTable . ColumnID = DELETED . ColumnID AND HistoryTable . EndDate IS NULLINSERTINTOHistoryTable(ColumnID,Column2,...,Columnn,StartDate,EndDate)SELECTColumnID,Column2,...,Columnn,@NOW,NULLFROMINSERTEDOld and New.DELIMITER$$/* Trigger for INSERT */CREATETRIGGERHistoryTableInsertAFTERINSERTONOriginalTableFOREACHROWBEGINDECLARENDATETIME;SETN=now();INSERTINTOHistoryTable(Column1,Column2,...,Columnn,StartDate,EndDate)VALUES(New.Column1,New.Column2,...,New.Columnn,N,NULL);END;/* Trigger for DELETE */CREATETRIGGERHistoryTableDeleteAFTERDELETEONOriginalTableFOREACHROWBEGINDECLARENDATETIME;SETN=now();UPDATEHistoryTableSETEndDate=NWHEREColumn1=OLD.Column1ANDEndDateISNULL;END;/* Trigger for UPDATE */CREATETRIGGERHistoryTableUpdateAFTERUPDATEONOriginalTableFOREACHROWBEGINDECLARENDATETIME;SETN=now();UPDATE HistoryTable SET EndDate = N WHERE Column1 = OLD . Column1 AND EndDate IS NULL ;INSERT INTO HistoryTable ( Column1 , Column2 , ..., Columnn , StartDate , EndDate ) VALUES ( New . Column1 , New . Column2 , ..., New . Columnn , N , NULL ); END ;:OLD古い値と新しい値は、およびと呼ばれるレコードデータ構造のフィールドとして公開されます:NEW。:NEWを定義するレコードのフィールドがNULLかどうかをテストする必要があります。これは、すべての列にNULL値を持つ新しい行が挿入されるのを防ぐためです。CREATE OR REPLACE TRIGGER TableTrigger AFTER INSERT OR UPDATE OR DELETE ON OriginalTable FOR EACH ROW DECLARE Now TIMESTAMP ; BEGIN SELECT CURRENT_TIMESTAMP INTO Now FROM Dual ;UPDATE HistoryTable SET EndDate = Now WHERE EndDate IS NULL AND Column1 = : OLD . Column1 ;IF : NEW . Column1 IS NOT NULL THEN INSERT INTO HistoryTable ( Column1 , Column2 , ..., Columnn , StartDate , EndDate ) VALUES (: NEW . Column1 , : NEW . Column2 , ..., : NEW . Columnn , Now , NULL ); END IF ; END ;old_tableがnew_table、これらの名前は異なる場合があります。CREATE OR REPLACE FUNCTION process_for_table () RETURNS TRIGGER AS $$ DECLARE now TIMESTAMP : = NOW (); BEGIN --- セクションの削除IF ( TG_OP = 'UPDATE' OR TG_OP = 'DELETE' ) THEN UPDATE HistoricTable SET EndDate = now FROM HistoricTable AS H INNER JOIN old_table ON H . ColumnID = old_table . ColumnID WHERE HistoricTable . ColumnID = H . ColumnID AND HistoricTable . EndDate IS NULL ; END IF ;--- セクションの挿入IF ( TG_OP = 'INSERT' OR TG_OP = 'UPDATE' ) THEN INSERT INTO HistoricTable SELECT ColumnID , Column2 , ..., Columnn , now , NULL FROM new_table ; END IF ; RETURN NULL ; END ; $$ LANGUAGE plpgsqlCREATE TRIGGER TriggerForTableInsert AFTER INSERT ON OriginalTable REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE FUNCTION process_for_table ();CREATE TRIGGER TriggerForTableUpdate AFTER UPDATE ON OriginalTable REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE FUNCTION process_for_table ();CREATE TRIGGER TriggerForTableDelete AFTER DELETE ON OriginalTable REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION process_for_table ();通常、データベースのバックアップは、過去の情報を保存および取得するために使用されます。データベースのバックアップは、すぐに使用できる過去の情報を効率的に取得する手段というだけでなく、セキュリティメカニズムとしての役割も果たします。
(完全な)データベースバックアップは、特定の時点におけるデータのスナップショットに過ぎません。そのため、各スナップショットの情報は把握できますが、スナップショット間の情報は把握できません。データベースバックアップに含まれる情報は、時間的に離散的です。
ログトリガーを使用すると、得られる情報は離散的ではなく連続的になり、任意の時点での情報の正確な状態を知ることができます。ただし、その情報は、使用するRDBMSのDATETIMEデータ型によって提供される時間の粒度に制限されます。
SELECT Column1 , Column2 , ..., Columnn FROM HistoryTable WHERE EndDate IS NULL元のテーブル全体の同じ結果セットを返す必要があります。
@DATE変数に、関心のある時点または時刻が含まれていると仮定します。
SELECT Column1 , Column2 , ..., Columnn FROM HistoryTable WHERE @ Date >= StartDate AND ( @ Date < EndDate OR EndDate IS NULL )@DATE変数には関心のある時点または時刻が含まれており、変数には関心のあるエンティティの主キー@KEYが含まれているとします。
SELECT Column1 , Column2 , ..., Columnn FROM HistoryTable WHERE Column1 = @ Key AND @ Date >= StartDate AND ( @ Date < EndDate OR EndDate IS NULL )その@KEY変数には、対象となるエンティティの主キーが含まれていると仮定します。
SELECT Column1 , Column2 , ..., Columnn , StartDate , EndDate FROM HistoryTable WHERE Column1 = @ Key ORDER BY StartDateその@KEY変数には、対象となるエンティティの主キーが含まれていると仮定します。
SELECT H2.Column1 , H2.Column2 , ... , H2.Columnn , H2.StartDate FROM HistoryTable AS H2 LEFT OUTER JOIN HistoryTable AS H1 ON H2.Column1 = H1.Column1 AND H2.Column1 = @Key AND H2.StartDate = H1.EndDate WHERE H2.EndDate IS NULLトリガーは主キーが常に同じであることを要求するため、主キーの不変性を確保または最大限に高めることが望ましい。主キーの値が変更されると、それが表すエンティティは自身の履歴を破壊してしまうからである。
主キーの不変性を実現または最大化するには、いくつかの方法があります。
徐々に変化しているディメンション管理手法によれば、ログトリガーは以下のように分類されます。
Logトリガーは、トランザクションデータベースの履歴を自動的に生成するためにLaurence R. Ugalde [ 3 ]によって設計されました。
GitHubのログトリガー