将来変更を参照できるように、すべての変更を追跡するデータベースを設計したいと考えています。だから例えば:
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
これらは、異なる時点で「同じエントリ」であったことを認識する必要があります。
編集:この履歴は、ある時点でユーザーに提示する必要があるため、理想的には、どの「リビジョンクラスター」に属する行であるかを理解しておく必要があります。
このデータベースの設計に取り組む最良の方法は何ですか?
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)
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データベースを使用するマイクロサービスと同様に機能します。
最善の方法は、あなたが何をしているかによって異なります。あなたはゆっくりと変化する次元をより深く見たいと思います:
https://en.wikipedia.org/wiki/Slowly_changing_dimension
Postgres 9.2では、tsrangeタイプもお見逃しなく。 start_date
およびend_date
を1つの列に入れ、Gist(またはGIN)インデックスを使用してものにインデックスを付け、除外制約と並べて日付範囲の重複を回避します。
編集:
同じ「リビジョンクラスター」に属する行を理解する必要があります。
この場合、リビジョン番号やライブフラグではなく、何らかの方法で表の日付範囲を希望します。それ以外の場合は、関連するあちこちのデータ。
別のメモとして、すべてを同じテーブルに格納するのではなく、監査テーブルをライブデータから区別することを検討してください。実装と管理は困難ですが、ライブデータに対するクエリがはるかに効率的になります。
この関連記事も参照してください: ひねりを加えた一時的なデータベース設計(ライブとドラフト行)
すべての変更をログに記録する方法の1つは、いわゆるaudit triggers
を作成することです。このようなトリガーは、それらが存在するテーブルへの変更を別のログテーブルに記録できます(クエリを実行して変更の履歴を確認できます)。
実装の詳細 here
。