128

プロジェクトには、エンティティのすべてのリビジョン(変更履歴)をデータベースに保存する必要があります。現在、これについて2つの設計された提案があります。

例:「従業員」エンティティの場合

デザイン1:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"

デザイン2:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- In this approach we have basically duplicated all the fields on Employees 
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName, 
      LastName, DepartmentId, .., ..)"

このことを行う他の方法はありますか?

「デザイン1」の問題は、データにアクセスする必要があるたびにXMLを解析する必要があることです。これにより、プロセスが遅くなり、リビジョンデータフィールドに結合を追加できないなどの制限も追加されます。

また、「デザイン2」の問題は、すべてのエンティティのすべてのフィールドを複製する必要があることです(リビジョンを維持したいエンティティは約70〜80個あります)。

4

16 に答える 16

58

ここでの重要な質問は、「誰が/何が歴史を利用するのか」ということだと思います。

主にレポート/人間が読める形式の履歴を対象とする場合は、過去にこのスキームを実装しました...

'AuditTrail'というテーブル、または次のフィールドを持つテーブルを作成します。

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[OldValue] [varchar](5000) NULL,
[NewValue] [varchar](5000) NULL

次に、すべてのテーブルに「LastUpdatedByUserID」列を追加できます。この列は、テーブルで更新/挿入を行うたびに設定する必要があります。

次に、すべてのテーブルにトリガーを追加して、発生する挿入/更新をキャッチし、変更されたフィールドごとにこのテーブルにエントリを作成できます。テーブルには更新/挿入ごとに「LastUpdateByUserID」も提供されているため、トリガーでこの値にアクセスして、監査テーブルに追加するときに使用できます。

RecordIDフィールドを使用して、更新されるテーブルのキーフィールドの値を格納します。結合されたキーの場合は、フィールド間に「〜」を使用して文字列を連結するだけです。

このシステムには欠点があると確信しています。データベースが大幅に更新されるとパフォーマンスが低下する可能性がありますが、私のWebアプリでは、書き込みよりも多くの読み取りが行われ、かなり良好に機能しているようです。テーブル定義に基づいてトリガーを自動的に書き込むための小さなVB.NETユーティリティも作成しました。

ちょっとした考え!

于 2008-09-02T11:46:44.827 に答える
41
  1. IsCurrent 識別子属性を持つ 1 つのテーブルにすべてを入れないでください。これは、後で問題を引き起こし、代理キーやその他のあらゆる種類の問題を必要とします。
  2. 設計 2 には、スキーマの変更に関する問題があります。Employees テーブルを変更する場合は、EmployeeHistories テーブルとそれに関連するすべての sproc を変更する必要があります。スキーマ変更の労力が 2 倍になる可能性があります。
  3. 設計 1 は適切に機能し、適切に実行された場合、パフォーマンス ヒットに関してそれほどコストはかかりません。xml スキーマやインデックスを使用して、起こりうるパフォーマンスの問題を解決できます。xml の解析に関するコメントは有効ですが、xquery を使用してビューを簡単に作成できます。これをクエリに含めて結合できます。このようなもの...
CREATE VIEW EmployeeHistory
AS
, FirstName, , DepartmentId

SELECT EmployeeId, RevisionXML.value('(/employee/FirstName)[1]', 'varchar(50)') AS FirstName,

  RevisionXML.value('(/employee/LastName)[1]', 'varchar(100)') AS LastName,

  RevisionXML.value('(/employee/DepartmentId)[1]', 'integer') AS DepartmentId,

FROM EmployeeHistories 
于 2008-09-02T12:13:47.380 に答える
24

DatabaseProgrammerブログのHistoryTablesの記事が役立つかもしれません-ここで提起されたポイントのいくつかをカバーし、デルタのストレージについて説明します。

編集

履歴テーブルのエッセイでは、著者(Kenneth Downs)は、少なくとも7列の履歴テーブルを維持することを推奨しています。

  1. 変更のタイムスタンプ、
  2. 変更を加えたユーザー、
  3. 変更されたレコードを識別するためのトークン(履歴は現在の状態とは別に維持されます)、
  4. 変更が挿入、更新、削除のいずれであったか、
  5. 古い値、
  6. 新しい価値、
  7. デルタ(数値の変更用)。

変更されない列、または履歴が不要な列は、肥大化を避けるために履歴テーブルで追跡しないでください。数値のデルタを保存すると、古い値と新しい値から導出できる場合でも、後続のクエリが簡単になります。

システム以外のユーザーが行を挿入、更新、または削除できないように、履歴テーブルは安全である必要があります。全体的なサイズを縮小するために(そしてユースケースで許可されている場合)、定期的なパージのみをサポートする必要があります。

于 2008-09-24T10:53:31.403 に答える
17

Chris Roberts が提案したソリューションと非常によく似たソリューションを実装しましたが、これは非常にうまく機能しています。

唯一の違いは、新しい値のみを保存することです。古い値は結局前の履歴行に保存されます

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[NewValue] [varchar](5000) NULL

20 列のテーブルがあるとします。この方法では、行全体を保存するのではなく、変更された正確な列のみを保存する必要があります。

于 2008-09-02T12:12:17.870 に答える
17

設計 1 は避けてください。たとえば、管理者コンソールを使用して自動または「手動」で古いバージョンのレコードにロールバックする必要がある場合は、あまり便利ではありません。

設計 2 の欠点はあまり見当たりません。2 番目の History テーブルには、最初の Records テーブルに存在するすべての列を含める必要があると思います。たとえば、mysql では、別のテーブルと同じ構造を持つテーブルを簡単に作成できます ( create table X like Y)。また、ライブ データベースの Records テーブルの構造を変更しようとしているときは、alter tableとにかくコマンドを使用する必要があります。これらのコマンドを History テーブルに対しても実行するのに大きな労力は必要ありません。

ノート

  • レコード テーブルには最新のリビジョンのみが含まれます。
  • History テーブルには、Records テーブル内のレコードの以前のリビジョンがすべて含まれています。
  • History テーブルの主キーは Records テーブルの主キーにRevisionId列を追加したものです。
  • ModifiedBy特定のリビジョンを作成したユーザーなど、追加の補助フィールドについて考えてみてください。DeletedBy誰が特定のリビジョンを削除したかを追跡するフィールドが必要な場合もあります。
  • DateModifiedこの特定のリビジョンが作成された場所を意味するか、この特定のリビジョンがいつ別のリビジョンに置き換えられたかを意味します。前者はフィールドが Records テーブルにある必要があり、一見するとより直感的に見えます。ただし、2 番目の解決策は、削除されたレコード (この特定のリビジョンが削除された日付) に対してより実用的であるようです。最初のソリューションを使用する場合は、おそらく 2 番目のフィールドが必要になるでしょうDateDeleted(もちろん必要な場合のみ)。あなたとあなたが実際に記録したいものに依存します。

設計 2 の操作は非常に簡単です。

変更
  • レコードを Records テーブルから History テーブルにコピーし、新しい RevisionId を与え (レコード テーブルにまだ存在しない場合)、DateModified を処理します (解釈方法によって異なります。上記の注を参照してください)。
  • Records テーブルのレコードの通常の更新を続行します
消去
  • 変更操作の最初のステップとまったく同じことを行います。選択した解釈に応じて、DateModified/DateDeleted を適切に処理します。
削除の取り消し (またはロールバック)
  • History テーブルから最高の (または特定の?) リビジョンを取得し、それを Records テーブルにコピーします。
特定のレコードの改訂履歴を一覧表示する
  • ヒストリーテーブルとレコードテーブルから選択
  • この操作に正確に何を期待するかを考えてください。おそらく、DateModified/DateDeleted フィールドから必要な情報を決定します (上記の注を参照)。

設計 2 を選択すると、そのために必要なすべての SQL コマンドが非常に簡単になり、メンテナンスも簡単になります。おそらく、レコード テーブルでも補助列 ( RevisionIdDateModified) を使用して、両方のテーブルをまったく同じ構造(一意のキーを除く)に保つと、はるかに簡単になります。これにより、単純な SQL コマンドが可能になり、データ構造の変更に耐性があります。

insert into EmployeeHistory select * from Employe where ID = XX

トランザクションを使用することを忘れないでください。

スケーリングに関しては、このソリューションは非常に効率的です。XML からデータを変換する必要がなく、テーブル行全体をコピーするだけなので (インデックスを使用した非常に単純なクエリ)、非常に効率的です。

于 2013-06-08T17:52:38.730 に答える
13

履歴を保存する必要がある場合は、追跡しているテーブルと同じスキーマと「改訂日」および「改訂タイプ」列(「削除」、「更新」など)を使用してシャドウテーブルを作成します。一連のトリガーを作成(または生成-以下を参照)して、監査テーブルにデータを入力します。

テーブルのシステムデータディクショナリを読み取り、シャドウテーブルとそれにデータを入力するための一連のトリガーを作成するスクリプトを生成するツールを作成するのはかなり簡単です。

このためにXMLを使用しようとしないでください。XMLストレージは、このタイプのトリガーが使用するネイティブデータベーステーブルストレージよりもはるかに効率が低くなります。

于 2010-09-23T08:22:46.287 に答える
9

ラメシュ、私は最初のアプローチに基づくシステムの開発に携わっていました。
XMLとしてリビジョンを保存すると、データベースが大幅に増加し、処理速度が大幅に低下することが判明しました。
私のアプローチは、エンティティごとに1つのテーブルを持つことです。

Employee (Id, Name, ... , IsActive)  

ここで、IsActiveは最新バージョンのサインです

追加情報をリビジョンに関連付ける場合は、その情報を含む別のテーブルを作成し、PK\FK関係を使用してエンティティテーブルにリンクできます。

このようにして、すべてのバージョンの従業員を1つのテーブルに格納できます。このアプローチの長所:

  • シンプルなデータベース構造
  • テーブルが追加専用になるため、競合は発生しません
  • IsActiveフラグを変更するだけで、以前のバージョンにロールバックできます。
  • オブジェクト履歴を取得するために結合する必要はありません

主キーが一意でないことを許可する必要があることに注意してください。

于 2008-09-02T11:49:28.767 に答える
7

私が過去にこれが行われたのを見た方法は

Employees (EmployeeId, DateModified, < Employee Fields > , boolean isCurrent );

このテーブルを「更新」することはなく(isCurrentの有効なものを変更する場合を除く)、新しい行を挿入するだけです。任意のEmployeeIdに対して、1行のみがisCurrent==1を持つことができます。

これを維持することの複雑さは、ビューと「代わりに」トリガーによって隠すことができます(Oracleでは、他のRDBMSと同様のことを想定しています)。テーブルが大きすぎてインデックスで処理できない場合は、マテリアライズドビューに移動することもできます) 。

この方法は問題ありませんが、複雑なクエリが発生する可能性があります。

個人的に、私はあなたのDesign 2のやり方がとても好きです。それは、私が過去にもやった方法です。理解しやすく、実装しやすく、保守も簡単です。

また、特に読み取りクエリを実行する場合、データベースとアプリケーションのオーバーヘッドはほとんど発生しません。これは、99%の時間で実行される可能性があります。

また、履歴テーブルとトリガーの作成を自動化して維持することも非常に簡単です(トリガーを介して行われると仮定します)。

于 2008-09-02T11:43:50.463 に答える
4

データの改訂は、時制データベースの「有効時間」の概念の1つの側面です。これについては多くの研究が行われ、多くのパターンとガイドラインが浮かび上がってきました。興味のある人のために、この質問への参照をたくさん添えて、長い返信を書きました。

于 2008-12-31T11:33:45.547 に答える
3

最初の1つを実行する場合は、EmployeesテーブルにもXMLを使用することをお勧めします。ほとんどの新しいデータベースでは、XMLフィールドにクエリを実行できるため、これが常に問題になるとは限りません。また、最新バージョンか以前のバージョンかに関係なく、従業員データにアクセスする1つの方法がある方が簡単な場合があります。

ただし、2番目のアプローチを試してみます。DateModifiedフィールドを持つEmployeesテーブルを1つだけ持つことで、これを単純化できます。EmployeeId + DateModifiedが主キーになり、行を追加するだけで新しいリビジョンを保存できます。このようにして、古いバージョンのアーカイブとアーカイブからのバージョンの復元も簡単になります。

これを行う別の方法は、 DanLinstedtによるdatavaultモデルである可能性があります。私はこのモデルを使用したオランダの統計局のプロジェクトを行いましたが、非常にうまく機能しています。しかし、私はそれが日々のデータベースの使用に直接役立つとは思いません。しかし、彼の論文を読むことでいくつかのアイデアが得られるかもしれません。

于 2008-09-02T11:48:34.510 に答える
3

実際に監査証跡だけが必要な場合は、監査テーブル ソリューションに傾倒します (他のテーブルの重要な列の非正規化されたコピーを完備していますUserName)。ただし、苦い経験から、単一の監査テーブルが将来的に大きなボトルネックになることを覚えておいてください。監査対象のすべてのテーブルに対して個別の監査テーブルを作成することはおそらく価値があります。

実際の履歴 (および/または将来) のバージョンを追跡する必要がある場合、標準的な解決策は、開始、終了、および期間の値の組み合わせを使用して、複数の行で同じエンティティを追跡することです。ビューを使用すると、現在の値に簡単にアクセスできます。これがあなたのアプローチである場合、バージョン管理されたデータが変更可能であるがバージョン管理されていないデータを参照すると、問題が発生する可能性があります。

于 2008-09-02T19:47:02.440 に答える
2

どうですか:

  • 従業員ID
  • 日付が変更されました
    • 追跡方法に応じて、および/またはリビジョン番号
  • ModifiedByUserId
    • 加えて、追跡したいその他の情報
  • 従業員フィールド

主キー (EmployeeId、DateModified) を作成し、「現在の」レコードを取得するには、各従業員 ID に対して MAX(DateModified) を選択するだけです。IsCurrent を格納することは非常に悪い考えです。まず第一に、それは計算できます。第二に、データが同期しなくなるのは非常に簡単です。

また、最新のレコードのみを一覧表示するビューを作成して、アプリでの作業中に主に使用することもできます。このアプローチの良い点は、データの重複がなく、すべての履歴やロールバックなどを取得するために 2 つの異なる場所 (Employees の current と EmployeesHistory のアーカイブ) からデータを収集する必要がないことです)。 .

于 2008-10-18T17:29:57.420 に答える
1

同様の要件がありましたが、多くの場合、ユーザーは変更内容を確認したいだけで、必ずしも変更をロールバックする必要はありません。

あなたのユースケースが何であるかはわかりませんが、私たちが行ったのは、外部キー参照と列挙のフレンドリ名を含む、ビジネスエンティティへの変更で自動的に更新される監査テーブルを作成することでした。

ユーザーが変更を保存するたびに、古いオブジェクトを再読み込みし、比較を実行し、変更を記録して、エンティティを保存します(問題が発生した場合に備えて、すべて単一のデータベーストランザクションで実行されます)。

これはユーザーにとって非常にうまく機能しているようであり、事業体と同じフィールドを持つ完全に別個の監査テーブルを持つという頭痛の種を省くことができます。

于 2008-09-02T11:43:55.190 に答える
0

ID 3、「bob」、「123 main street」、次に別のID 3、「bob」、「234 elm st」など、特定のエンティティへの変更を経時的に追跡したいようです。すべてのアドレス「bob」があったことを示す改訂履歴を吐き出します。

これを行うための最良の方法は、各レコードに「現在」フィールドを設定し、(おそらく)日付/時刻テーブルへのタイムスタンプまたはFKを設定することです。

次に、インサートは「現在」を設定し、前の「現在」レコードの「現在」の設定を解除する必要があります。すべての履歴が必要な場合を除き、クエリでは「現在」を指定する必要があります。

テーブルが非常に大きい場合、または多数のリビジョンが予想される場合は、これをさらに微調整しますが、これはかなり標準的なアプローチです。

于 2008-09-02T11:47:14.667 に答える