web-dev-qa-db-ja.com

データベース内のレコードをバージョン管理する方法

データベースにレコードがあり、管理者と通常のユーザーの両方が更新できるとしましょう。

誰でも、この表のすべての変更をバージョン管理する方法/アーキテクチャを提案して、レコードを前のリビジョンにロールバックできるようにすることができます。

154
Niels Bosma

管理者とユーザーが更新できるFOOテーブルがあるとします。ほとんどの場合、FOOテーブルに対してクエリを作成できます。幸せな日々。

次に、FOO_HISTORYテーブルを作成します。これには、FOOテーブルのすべての列があります。主キーは、FOOにRevisionNumber列を加えたものと同じです。 FOO_HISTORYからFOOへの外部キーがあります。 UserIdやRevisionDateなど、リビジョンに関連する列を追加することもできます。すべての*_HISTORYテーブルにわたって(つまり、Oracleシーケンスまたは同等のものから)常にRevisionNumbersを増やします。 1秒に1つの変更しかないことに依存しないでください(つまり、RevisionDateを主キーに入れないでください)。

これで、FOOを更新するたびに、更新を行う直前に、古い値をFOO_HISTORYに挿入します。プログラマがこのステップを誤って見逃すことがないように、設計の基本レベルでこれを行います。

FOOから行を削除する場合、いくつかの選択肢があります。すべての履歴をカスケードして削除するか、FOOに削除済みのフラグを立てて論理削除を実行します。

このソリューションは、現在の値に主に興味があり、履歴にたまにしか興味がない場合に適しています。常に履歴が必要な場合は、有効な開始日と終了日を入力し、FOO自体にすべてのレコードを保持できます。すべてのクエリは、それらの日付を確認する必要があります。

153
WW.

誰かが質問/回答を編集するときにStackOverflowが行うように、データベースレコードのコンテンツのバージョン管理を探していると思います。適切な出発点は、revision追跡を使用するデータベースモデルを調べることです。

思い浮かぶ最高の例は、WikipediaエンジンであるMediaWikiです。データベースダイアグラム こちら 、特に リビジョンテーブル を比較してください。

使用しているテクノロジーに応じて、いくつかの優れたdiff/mergeアルゴリズムを見つける必要があります。

.NETの場合は、 この質問 を確認してください。

40
CMS

BIの世界では、バージョン管理するテーブルにstartDateとendDateを追加することでこれを実現できます。テーブルに最初のレコードを挿入すると、startDateが入力されますが、endDateはnullです。 2番目のレコードを挿入すると、最初のレコードのendDateも2番目のレコードのstartDateで更新されます。

現在のレコードを表示するには、endDateがnullのレコードを選択します。

これはタイプ2と呼ばれることもあります- 緩やかに変化する次元TupleVersioning も参照してください

25
Dave Neeley

SQL 2008にアップグレードします。

SQL 2008でSQL変更追跡を使用してみてください。タイムスタンプと廃棄の列ハッキングの代わりに、この新しい機能を使用してデータベース内のデータの変更を追跡できます。

MSDN SQL 2008変更追跡

9
D3vtr0n

この問題に対する良い解決策の1つを追加したいのは、 Temporal database を使用することです。多くのデータベースベンダーは、この機能をすぐに使用できるようにするか、拡張機能を介して提供しています。 PostgreSQLで temporal table 拡張機能を使用できましたが、他の拡張機能もあります。データベースのレコードを更新するたびに、データベースはそのレコードの前のバージョンも保持します。

6
wuher

2つのオプション:

  1. 履歴テーブルがある-元のデータが更新されるたびに、この履歴テーブルに古いデータを挿入します。
  2. 監査テーブル-変更前と変更後の値を保存します-監査テーブル内の変更された列だけでなく、誰がいつ更新したかなどの情報も保存します。
5
alok

SQLトリガーを介してSQLテーブルで監査を実行できます。トリガーから、2つの特別なテーブルにアクセスできます( 挿入および削除 )。これらのテーブルには、テーブルが更新されるたびに挿入または削除された正確な行が含まれます。トリガーSQLでは、これらの変更された行を取得して監査テーブルに挿入できます。このアプローチは、監査がプログラマーに対して透過的であることを意味します。それらからの努力や実装知識を必要としません。

このアプローチの追加のボーナスは、SQL操作がデータアクセスDLLを介して行われたか、手動のSQLクエリを介して行われたかに関係なく監査が行われることです。 (監査はサーバー自体で実行されるため)。

4
Doctor Jones

あなたはどのデータベースを言っていない、そして私は投稿タグにそれを見ない。 Oracleの場合は、Designerに組み込まれているアプローチ journal tables を使用することをお勧めします。他のデータベース用の場合は、基本的に同じ方法をお勧めします...

別のDBに複製したい場合、または単に理解したい場合、それが機能する方法は、テーブルには同じフィールド仕様の通常のデータベーステーブルだけでなくシャドウテーブルも作成されることです、いくつかの追加フィールド:最後に実行されたアクション(文字列、挿入の場合は「INS」、更新の場合は「UPD」、削除の場合は「DEL」)、アクションが実行された日時、および実行されたユーザーのIDそれ。

トリガーを介して、everyテーブル内の任意の行に対するアクションは、新しい値、実行されたアクション、いつ、およびどのユーザーによって。行を削除することはありません(少なくとも過去数か月間は)。はい、大きくなり、数百万行になりますが、anyレコードの値はで簡単に追跡できますジャーナリングが開始された後、または古いジャーナル行が最後にパージされてからの任意の時点、および最後の変更を行った人。

Oracleでは、必要なものはすべてSQLコードとして自動的に生成されます。必要なのは、コンパイル/実行するだけです。そして、それを検査するための基本的なCRUDアプリケーション(実際には「R」のみ)が付属しています。

3
bart

私も同じことをしています。授業計画用のデータベースを作成しています。これらの計画には、アトミックな変更バージョン管理の柔軟性が必要です。言い換えれば、レッスンプランの変更はどれほど小さくても許可する必要がありますが、古いバージョンもそのまま維持する必要があります。これにより、レッスン作成者は、生徒が使用している間にレッスン計画を編集できます。

それが機能する方法は、学生がレッスンを行うと、彼らの結果は完了したバージョンに添付されるということです。変更が行われた場合、結果は常にバージョンを指します。

このように、レッスン条件が削除または移動されても、結果は変わりません。

現在これを行う方法は、1つのテーブルですべてのデータを処理することです。通常、idフィールドは1つだけですが、このシステムでは、idとsub_idを使用しています。 sub_idは、更新および削除の間、常に行にとどまります。 IDは自動インクリメントされます。レッスン計画ソフトウェアは最新のsub_idにリンクします。学生の結果はIDにリンクします。また、変更が発生したタイミングを追跡するためのタイムスタンプも含めましたが、バージョン管理を処理する必要はありません。

変更したら、テストしたら、前述のendDate nullアイデアを使用できます。私のシステムでは、最新バージョンを見つけるには、max(id)を見つける必要があります。もう一方のシステムは、endDate = nullを探すだけです。別の日付フィールドがあることで利益が出るかどうかはわかりません。

私の2セント。

2
Jordan

@WWながら。別の方法は、バージョン列を作成し、すべてのバージョンを同じテーブルに保持することです。

1つのテーブルアプローチの場合:

  • フラグを使用して最新のalaを示します Word Press
  • または、バージョンouter joinよりも大きな厄介な処理を行います。

リビジョン番号を使用する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つの列のみを更新し、楽観的なロックやトランザクションを避けたい場合に特に役立ちます。

フラグアプローチと履歴テーブルアプローチでは、two行を挿入/更新する必要があります。

outer joinリビジョン番号アプローチのもう1つの利点は、トリガーが本質的に上記のような処理を行う必要があるため、後でトリガーを使用していつでも複数テーブルアプローチにリファクタリングできることです。

2
Adam Gent

Alokが提案したAudit table上記の投稿で説明したいと思います。

このスキーマレスの単一テーブル設計をプロジェクトに採用しました。

スキーマ:

  • id-整数自動インクリメント
  • ユーザー名-STRING
  • テーブル名-STRING
  • oldvalue-TEXT/JSON
  • newvalue-TEXT/JSON
  • createdon-DATETIME

このテーブルは、各テーブルの履歴レコードをすべて一度に保持し、完全なオブジェクト履歴を1つのレコードに保持できます。このテーブルは、データが変更され、ターゲット行の古い値と新しい値のスナップショットを保存するトリガー/フックを使用して作成できます。

この設計の長所:

  • 履歴管理のために管理するテーブルの数が少なくなります。
  • 各行の古い状態と新しい状態の完全なスナップショットを保存します。
  • 各テーブルで簡単に検索できます。
  • テーブルごとにパーティションを作成できます。
  • テーブルごとにデータ保持ポリシーを定義できます。

この設計の短所:

  • システムに頻繁な変更がある場合、データサイズが大きくなる可能性があります。
2
Hassan Farid