回答:
FOO
管理者とユーザーが更新できるテーブルがあるとしましょう。ほとんどの場合、FOOテーブルに対するクエリを記述できます。幸せな日々。
次に、FOO_HISTORY
テーブルを作成します。これには、FOO
テーブルのすべての列があります。主キーはFOOと同じで、RevisionNumber列があります。からFOO_HISTORY
への外部キーがありFOO
ます。UserIdやRevisionDateなど、リビジョンに関連する列を追加することもできます。すべての*_HISTORY
テーブル(Oracleシーケンスまたは同等のもの)全体で、RevisionNumbersを常に増やしていきます。1秒間に1つの変更のみがあるとは考えないでください(つまりRevisionDate
、主キーに入れないでください)。
更新するたびにFOO
、更新を行う直前に古い値をに挿入しますFOO_HISTORY
。これは、プログラマーが誤ってこのステップを見落とさないように、設計の基本的なレベルで行います。
行を削除するFOO
場合は、いくつかの選択肢があります。カスケードしてすべての履歴を削除するか、削除済みFOO
としてフラグを付けて論理削除を実行します。
このソリューションは、現在の値に大きな関心があり、履歴にたまにしか関心がない場合に適しています。常に履歴が必要な場合は、有効な開始日と終了日を設定し、すべてのレコードをそのまま保持できFOO
ます。次に、すべてのクエリでこれらの日付を確認する必要があります。
There is a foreign key from FOO_HISTORY to FOO'
:悪い考えです。履歴を変更せずにfooからレコードを削除したいと思います。通常の使用では、履歴テーブルは挿入専用でなければなりません。
BIの世界では、バージョン管理するテーブルにstartDateとendDateを追加することでこれを実現できます。最初のレコードをテーブルに挿入すると、startDateに値が入力されますが、endDateはnullです。2番目のレコードを挿入すると、最初のレコードのendDateも2番目のレコードのstartDateで更新されます。
現在のレコードを表示する場合は、endDateがnullのレコードを選択します。
これは、タイプ2の緩やかに変化するディメンションと呼ばれることもあります。TupleVersioningも参照してください
SQL 2008にアップグレードします。
SQL 2008でSQL変更追跡を使用してみてください。タイムスタンプや廃棄列のハックの代わりに、この新機能を使用して、データベース内のデータの変更を追跡できます。
この問題に対する1つの優れた解決策は、Temporalデータベースを使用することです。多くのデータベースベンダーは、この機能をそのまま、または拡張機能を介して提供しています。私はPostgreSQLでテンポラルテーブル拡張を正常に使用しましたが、他の人にもそれがあります。データベースのレコードを更新すると、データベースはそのレコードの以前のバージョンも保持します。
SQLトリガーを介してSQLテーブルの監査を実行できます。トリガーから、2つの特別なテーブル(挿入および削除)にアクセスできます。これらのテーブルには、テーブルが更新されるたびに挿入または削除された正確な行が含まれています。トリガーSQLでは、これらの変更された行を取得して、監査テーブルに挿入できます。このアプローチは、監査がプログラマーに対して透過的であることを意味します。それらからの努力や実装に関する知識は必要ありません。
このアプローチの追加の利点は、SQL操作がデータアクセスDLLを介して行われたか、手動のSQLクエリを介して行われたかに関係なく、監査が行われることです。(監査はサーバー自体で実行されるため)。
あなたはどのデータベースを言うのではなく、私はそれを投稿タグに表示しません。Oracleの場合は、Designerに組み込まれているアプローチとしてジャーナルテーブルを使用することをお勧めします。他のデータベースの場合も、基本的には同じ方法をお勧めします...
別のDBに複製したい場合、または単に理解したい場合の動作方法は、テーブルにはシャドウテーブルも作成され、同じフィールド仕様の通常のデータベーステーブルのみが作成されます。 、およびいくつかの追加フィールド:最後に実行されたアクション(文字列、挿入の場合は通常の値「INS」、更新の場合は「UPD」、削除の場合は「DEL」)、アクションが実行された日時、実行したユーザーのユーザーIDなどそれ。
トリガーを介して、テーブルの任意の行に対するすべてのアクションは、新しい値、どのアクションがいつ、どのユーザーによって実行されたかを示すジャーナルテーブルに新しい行を挿入します。行を削除しない(少なくとも過去数か月は)。はい、それは大きくなり、数百万行になりやすくなりますが、ジャーナリングが開始されてから、または古いジャーナル行が最後にパージされてから、いつ誰が最後に変更を行ったかから、いつでも任意のレコードの値を簡単に追跡できます。
Oracleでは、必要なすべてのものがSQLコードとして自動的に生成され、コンパイル/実行するだけで済みます。それを検査するための基本的なCRUDアプリケーション(実際には「R」のみ)が付属しています。
私も同じことをしています。授業計画のデータベースを作っています。これらの計画には、アトミック変更のバージョン管理の柔軟性が必要です。言い換えれば、レッスンプランの変更は、どれだけ小さくても許可する必要がありますが、古いバージョンもそのままにしておく必要があります。このようにして、レッスン作成者は、生徒が使用しているときにレッスンプランを編集できます。
それが機能する方法は、学生がレッスンを完了すると、彼らの結果は彼らが完成したバージョンに添付されるということです。変更が加えられた場合、結果は常にバージョンを示します。
このようにして、レッスン基準が削除または移動されても、結果は変わりません。
私が現在これを行っている方法は、1つのテーブルですべてのデータを処理することです。通常、idフィールドは1つだけですが、このシステムでは、idとsub_idを使用しています。sub_idは、更新と削除を通じて、常に行に残ります。IDは自動的に増分されます。授業計画ソフトウェアは最新のsub_idにリンクします。学生の結果はIDにリンクされます。変更がいつ発生したかを追跡するためのタイムスタンプも含めましたが、バージョン管理を行う必要はありません。
私が変更した後、変更する可能性があることの1つは、前述のendDate nullのアイデアを使用することです。私のシステムでは、最新バージョンを見つけるために、max(id)を見つける必要があります。他のシステムは単にendDate = nullを探します。別の日付フィールドがあるためにメリットが出るかどうかはわかりません。
私の2セント。
@WWながら。答えは良い答えです。もう1つの方法は、バージョン列を作成し、すべてのバージョンを同じテーブルに保持することです。
1つのテーブルアプローチでは、次のいずれかを行います。
outer join
リビジョン番号を使用するメソッドのSQLの例は次のとおりです。
SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL
AND tc.path = '/stuff' -- path in this case is our natural id.
悪いニュースは、上記はを必要としouter join
、外部結合は遅くなる可能性があることです。良い知らせは、トランザクションなしで1つの書き込み操作でそれを行うことができるため(データベースがアトミックであると仮定すると)、新しいエントリの作成は理論的に安価です。
の新しいリビジョンを作成する例は'/stuff'
次のとおりです。
INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now()
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL
AND tc.path = '/stuff' -- {path}
)
古いデータを使用して挿入します。これは、1つの列のみを更新し、楽観的ロックやトランザクションを回避したい場合に特に便利です。
フラグアプローチと履歴テーブルアプローチでは、2つの行を挿入/更新する必要があります。
outer join
リビジョン番号アプローチのもう1つの利点は、トリガーを使用して常に複数テーブルのアプローチにリファクタリングできることです。これは、トリガーが本質的に上記のようなことを行う必要があるためです。
AlokはAudit table
上記の提案をしたので、私の投稿で説明したいと思います。
私はこのスキーマレスの単一テーブルデザインをプロジェクトに採用しました。
スキーマ:
このテーブルは、各テーブルの履歴レコードを一度にすべて保持でき、1つのレコードで完全なオブジェクト履歴を保持できます。このテーブルは、データが変化するトリガー/フックを使用してデータを入力でき、ターゲット行の古い値と新しい値のスナップショットを格納します。
このデザインの長所:
このデザインの短所: