web-dev-qa-db-ja.com

変更履歴を使用したデータベース設計

将来変更を参照できるように、すべての変更を追跡するデータベースを設計したいと考えています。だから例えば:

Database A 

+==========+========+==========+
|   ID     |  Name  | Property |

     1        Kyle      30

行の「プロパティ」フィールドを50に変更すると、行が次のように更新されます。

1    Kyle    50

しかし、行のプロパティがある時点で30であったという事実を保存する必要があります。次に、行が再び70に更新された場合:

1    Kyle    70

行のプロパティが50と70であるという両方の事実を保持する必要があります。そのため、いくつかのクエリで取得できます。

1    Kyle    30
1    Kyle    50

これらは、異なる時点で「同じエントリ」であったことを認識する必要があります。

編集:この履歴は、ある時点でユーザーに提示する必要があるため、理想的には、どの「リビジョンクラスター」に属する行であるかを理解しておく必要があります。

このデータベースの設計に取り組む最良の方法は何ですか?

23
Delos Chang

1つの方法は、データベースのすべてのテーブルにMyTableNameHistoryを設定し、そのスキーマをテーブルMyTableNameのスキーマと同一にすることです。ただし、履歴テーブルの主キーには、 effectiveUtcをDateTimeとして。たとえば、Employeeという名前のテーブルがある場合、

Create Table Employee
{
  employeeId integer Primary Key Not Null,
  firstName varChar(20) null,
  lastName varChar(30) Not null,
  HireDate smallDateTime null,
  DepartmentId integer null
}

次に、履歴テーブルは

Create Table EmployeeHistory
{
  employeeId integer Not Null,
  effectiveUtc DateTime Not Null,
  firstName varChar(20) null,
  lastName varChar(30) Not null,
  HireDate smallDateTime null,
  DepartmentId integer null,
  Primary Key (employeeId , effectiveUtc)
}

次に、従業員テーブルにトリガーを配置して、従業員テーブルに何かを挿入、更新、または削除するたびに、すべての通常のフィールドと現在のフィールドにまったく同じ値を持つ新しいレコードがEmployeeHistoryテーブルに挿入されるようにします。 effectiveUtc列のUTC日時。

次に、過去の任意の時点の値を見つけるには、asU日時の前の値として最も高い値がeffectiveUtc値であるレコードを履歴テーブルから選択するだけです。

 Select * from EmployeeHistory h
 Where EmployeeId = @EmployeeId
   And effectiveUtc =
    (Select Max(effectiveUtc)
     From EmployeeHistory 
     Where EmployeeId = h.EmployeeId
        And effcetiveUtc < @AsOfUtcDate) 
21
Charles Bretana

Charles 'answer に追加するには、データベース内の他のすべてのテーブルに異なる履歴テーブルを作成する代わりに、 Entity-Attribute-Valueモデル を使用します。

基本的に、oneHistoryテーブルを次のように作成します。

Create Table History
{
  tableId varChar(64) Not Null,
  recordId varChar(64) Not Null,
  changedAttribute varChar(64) Not Null,
  newValue varChar(64) Not Null,
  effectiveUtc DateTime Not Null,
  Primary Key (tableId , recordId , changedAttribute, effectiveUtc)
}

次に、いずれかのテーブルでcreateまたはmodifyデータをいつでもHistoryレコードを作成します。

例に従って、「カイル」をEmployeeテーブルに追加すると、2つのレコード(非ID属性ごとに1つ)が作成され、プロパティが変更されるたびに新しいレコードが作成されます。

History 
+==========+==========+==================+==========+==============+
| tableId  | recordId | changedAttribute | newValue | effectiveUtc |
| Employee | 1        | Name             | Kyle     | N            |
| Employee | 1        | Property         | 30       | N            |
| Employee | 1        | Property         | 50       | N+1          |
| Employee | 1        | Property         | 70       | N+2          |

または、 a_horse_with_no_nameこのコメント で提案されているように、フィールドの変更ごとに新しいHistoryレコードを保存しない場合は、グループ化された変更を保存できます(同じ更新でNameを 'Kyle'に、Propertyを30に変更するなど)単一のレコードとして。この場合、変更のコレクションをJSONまたはその他のblob形式で表現する必要があります。これにより、changedAttributeフィールドとnewValueフィールドが1つに統合されます(changedValues)。例えば:

History 
+==========+==========+================================+==============+
| tableId  | recordId | changedValues                  | effectiveUtc |
| Employee | 1        | { Name: 'Kyle', Property: 30 } | N            |

これは、データベース内の他のすべてのテーブルの履歴テーブルを作成するよりも難しいかもしれませんが、いくつかの利点があります。

  • データベースのテーブルに新しいフィールドを追加しても、同じフィールドを別のテーブルに追加する必要はありません
  • 使用するテーブルが少ない
  • さまざまなテーブルへの更新を時間をかけて相関させる方が簡単です

この設計のアーキテクチャ上の利点の1つは、アプリと履歴/監査機能の懸念を切り離すことです。この設計は、アプリケーションデータベースとは別のリレーショナルデータベースまたはNoSQLデータベースを使用するマイクロサービスと同様に機能します。

6
Luke

最善の方法は、あなたが何をしているかによって異なります。あなたはゆっくりと変化する次元をより深く見たいと思います:

https://en.wikipedia.org/wiki/Slowly_changing_dimension

Postgres 9.2では、tsrangeタイプもお見逃しなく。 start_dateおよびend_dateを1つの列に入れ、Gist(またはGIN)インデックスを使用してものにインデックスを付け、除外制約と並べて日付範囲の重複を回避します。


編集:

同じ「リビジョンクラスター」に属する行を理解する必要があります。

この場合、リビジョン番号やライブフラグではなく、何らかの方法で表の日付範囲を希望します。それ以外の場合は、関連するあちこちのデータ。

別のメモとして、すべてを同じテーブルに格納するのではなく、監査テーブルをライブデータから区別することを検討してください。実装と管理は困難ですが、ライブデータに対するクエリがはるかに効率的になります。


この関連記事も参照してください: ひねりを加えた一時的なデータベース設計(ライブとドラフト行)

2

すべての変更をログに記録する方法の1つは、いわゆるaudit triggersを作成することです。このようなトリガーは、それらが存在するテーブルへの変更を別のログテーブルに記録できます(クエリを実行して変更の履歴を確認できます)。

実装の詳細 here

1