データベース設計:「アーカイブ」問題の処理方法


18

多くのアプリケーション、重要なアプリケーション、銀行などがこれを日常的に行っていると確信しています。

すべての背後にある考え方は次のとおりです。

  • すべての行に履歴が必要です
  • すべてのリンクは一貫性を保つ必要があります
  • 「現在の」列を取得する要求を簡単に行う必要があります
  • 陳腐化したものを購入したクライアントは、この製品がカタログの一部ではなくなっても、購入したものを見る必要があります。

等々。

これが私がやりたいことであり、私が直面している問題を説明します。

すべてのテーブルにこれらの列があります。

  • id
  • id_origin
  • date of creation
  • start date of validity
  • start end of validity

また、CRUD操作のアイデアは次のとおりです。

  • create = id_origin= iddate of creation= now、start date of validity= now、end date of validity= nullで新しい行を挿入します(=は現在のアクティブなレコードであることを意味します)
  • 更新=
    • read = = end date of validitynullですべてのレコードを読み取ります
    • 「現在の」レコードend date of validity= nullをend date of validity= nowで更新する
    • 新しい値で新しいものを作成し、end date of validity= null(=は現在のアクティブなレコードであることを意味します)
  • 削除=「現在の」レコードend date of validityend date of validity= nowで= null 更新

だからここに私の問題があります:多対多の関連付け。値を使って例を見てみましょう:

  • テーブルA(id = 1、id_origin = 1、start = now、end = null)
  • テーブルA_B(start = now、end = null、id_A = 1、id_B = 48)
  • テーブルB(id = 48、id_origin = 48、start = now、end = null)

ここで、テーブルAを更新し、レコードID = 1にします

  • レコードID = 1をend = nowでマークします
  • テーブルAに新しい値を挿入し、...リレーションを複製しない限り、リレーションA_Bを失ってしまった...これはテーブルになります:

  • テーブルA(id = 1、id_origin = 1、start = now、end = now + 8mn)

  • テーブルA(id = 2、id_origin = 1、start = now + 8mn、end = null)
  • テーブルA_B(start = now、end = null、id_A = 1、id_B = 48)
  • テーブルA_B(start = now、end = null、id_A = 2、id_B = 48)
  • テーブルB(id = 48、id_origin = 48、start = now、end = null)

そして...まあ私は別の問題があります:関係A_B:私は(id_A = 1、id_B = 48)を廃止するかどうか(A-id = 1は廃止されますが、B-48ではない)とマークしますか?

これに対処するには?

これを大規模に設計する必要があります。製品、パートナーなどです。

これに関するあなたの経験は何ですか?どうしますか(どうしましたか)?

-編集

この非常に興味深い記事を見つけましたが、「カスケード陳腐化」に適切に対応していません(=私が実際に求めていること)


更新レコードのデータを、id_hist_prevフィールドを持つ履歴のリンクリストを保持する新しいIDを持つ新しいレコードに更新する前にコピーする方法はどうですか。したがって、現在のレコードのIDは変更されません

車輪を再発明するのではなく、たとえば、OracleのFlashback Data Archiveの使用を検討しましたか?
ジャックダグラス

回答:


4

これらの要件が監査目的なのか、CRMやショッピングカートなどの単純な履歴参照のためなのかは明確ではありません。

いずれにせよ、これが必要な各主要領域にmainおよびmain_archiveテーブルを用意することを検討してください。「メイン」には現在/アクティブなエントリのみがあり、「メインアーカイブ」にはこれまでにメインに入ったすべてのコピーが含まれます。main_archiveへの挿入/更新は、mainへの挿入/更新からのトリガーになります。これにより、main_archiveに対する削除は、より長い期間にわたって実行できます。

Cust XがProduct Yを購入したなどの参照の問題について、cust_archive-> product_archiveの参照の懸念を解決する最も簡単な方法は、product_archiveからエントリを削除しないことです。通常、解約はそのテーブルではるかに低くする必要がありますので、サイズが問題になりすぎないようにしてください。

HTH。


2
すばらしい答えですが、アーカイブテーブルを持つことのもう1つの利点は、非正規化される傾向があり、そのようなデータに関するレポートがより効率的になることです。このアプローチでも、アプリケーションのレポートのニーズを考慮してください。
maple_shaft

1
私が設計するほとんどのデータベースでは、すべての「メイン」テーブルにはのような製品名のプレフィックスがあり、LP_すべての重要なテーブルには同等のがありLH_、挿入、更新、削除時にトリガーが履歴行を挿入します。すべてのケースで機能するわけではありませんが、私がやっていることの堅実なモデルでした。

私は同意します-クエリの大部分が「現在の」行に対するものである場合、2つのテーブルの履歴から現在のパーティションを作成することにより、おそらくパフォーマンス上の利点が得られます。ビューは、便宜上、それらを結合することができます。この方法では、現在の行を含むデータページがすべて揃っており、おそらくキャッシュ内にとどまる可能性が高く、日付ロジックを使用して現在のデータのクエリを常に修飾する必要はありません。
-onupdatecascade

1
@onupdatecascade:(少なくとも一部のRDBMSでは)そのUNIONビューにインデックスを付けることができることに注意してください。これにより、現在のレコードと履歴レコードの両方に一意の制約を適用するなどのクールなことができます。
すべての取引のジョン

5年後、私はたくさんのことをやりました。いつもあなたのアイデアを取り戻しました。変更した唯一のことは、履歴テーブルに「id」と「id_ref」の列があることです。id_refテーブルの実際のアイデアへの参照です。例:personおよびperson_h。にperson_hid」と「id_ref」がid_refあり、「」に関連しperson.idているため、同じperson.id(=の行personが変更された場合)で多くの行を持つことができid、すべてのテーブルのすべてがautoincです。
オリビエポンス

2

これは、関数型プログラミングと一部重複しています。特に不変性の概念。

1つのテーブルが呼び出されPRODUCT、別のテーブルが呼び出されるPRODUCTVERSIONか、同様のテーブルがあります。製品を変更しても、更新は行わず、新しいPRODUCTVERSION行を挿入するだけです。最新を取得するには、バージョン番号(desc)、タイムスタンプ(desc)でテーブルにインデックスを付けるか、フラグ(LatestVersion)を使用します。

これで、製品を参照するものがある場合、それが指すテーブルを決定できます。それが指すんPRODUCTエンティティ(必ず本製品をいう。)またはにPRODUCTVERSIONエンティティ(唯一の製品のこのバージョンを指しますか)?

複雑になります。製品の写真がある場合はどうなりますか?それらは変更される可能性があるため、バージョンテーブルをポイントする必要がありますが、多くの場合、変更されないため、不必要にデータを複製することは望ましくありません。つまり、PICTUREテーブルとPRODUCTVERSIONPICTURE多対多の関係が必要です。


1

すべてのテーブルにある4つのフィールドを使用して、ここからすべてのものを実装しました。

  • id
  • date_creation
  • date_validity_start
  • date_validity_end

たびに、レコードがマークし、私はそれを複製し、修正しなければならない重複した「古い」=として記録をdate_validity_end=NOW()して良いものとして、現在の1 date_validity_start=NOW()date_validity_end=NULL

秘Theは、多対多および1対多の関係についてです:それらに触れることなく機能します!より複雑なクエリがすべてです:正確な日付(=現在ではない)でレコードをクエリするには、各結合およびメインテーブルにこれらの制約を追加する必要があります。

WHERE (
  (date_validity_start<=:dateparam AND date_validity_end IS NULL)
  OR
  (date_validity_start<=:dateparam AND date_validity_start>=:dateparam)
)

製品と属性(多対多の関係)の場合:

SELECT p.*,a.*

FROM products p

JOIN products_attributes pa
ON pa.id_product = p.id
AND (
  (pa.date_validity_start<=:dateparam AND pa.date_validity_end IS NULL)
  OR
  (pa.date_validity_start<=:dateparam AND pa.date_validity_start>=:dateparam)
)

JOIN attributes a
ON a.id = pa.id_attribute
AND (
  (a.date_validity_start<=:dateparam AND a.date_validity_end IS NULL)
  OR
  (a.date_validity_start<=:dateparam AND a.date_validity_start>=:dateparam)
)

WHERE (
  (p.date_validity_start<=:dateparam AND p.date_validity_end IS NULL)
  OR
  (p.date_validity_start<=:dateparam AND p.date_validity_start>=:dateparam)
)

0

これはどう?それは私が過去にやったことに対して、シンプルで非常に効果的だ。「履歴」テーブルで、別のPKを使用します。したがって、「CustomerID」フィールドはCustomerテーブルのPKですが、「history」テーブルでは、PKは「NewCustomerID」です。「CustomerID」は単なる読み取り専用フィールドになります。これにより、「CustomerID」は履歴に変更されず、すべての関係はそのまま残ります。


とてもいいアイデアです。私がやったことは非常に似ています:レコードを複製し、新しいレコードを「廃止」としてマークして、現在のレコードが同じになるようにします。注各テーブルにトリガーを作成したかったのですが、このテーブルのトリガーになっている場合、mysqlはテーブルの変更を禁止します。PostGRESQLはこれを行います。SQLサーバーがこれを行います。Oracleはこれを行います。簡単に言えば、MySQLにはまだ非常に長い道のりがあり、次回はデータベースサーバーを選択するときによく考えます。
オリビエポンス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.