web-dev-qa-db-ja.com

製品の価格変更履歴を保持する場合、価格変更イベントのみを追跡することの賛否両論は何ですか?

リレーショナルデータベースで価格設定の履歴などを追跡する場合、価格設定の変更を保存するために推奨される方法は何ですかたとえば、次のことができます。

  • 価格変更イベントのみを保持します(別名、新しい価格が入力されたことを示す単一の日付)。 1つの価格履歴レコードごとに1つの日付。別名price_change_date

または

  • 保つ effective_price_start_dateおよびeffective_price_end_date(別名、新しい価格を入力すると、古いレコードは「終了日」を取得および更新し、新しいレコードは「開始日」として現在の日付を取得し、「[終了日]」はnull(?)を取得します)。これは、各価格履歴レコードの2つの日付です。

私は最初のアプローチを考えています-「これは価格が入力された日付です」を意味する単一の日付レコードのみを保持することで十分です。そして、おそらくメンテナンスが少し簡単です。

しかし、価格の有効期間、つまり価格の開始日と終了日を維持することを推奨する他の回答の提案を見ました。しかし、そのアプローチには次のような複雑な問題があります

  • 以前の価格に戻したい場合はどうなりますか?価格が同じで日付が異なる新しいレコードを作成するだけですか?
  • 私は今、次のようなコーナーの問題に対処する必要があります-新しい価格を入力したばかりですが、それが確定していない場合、有効な終了日には何を配置すればよいですか? nullを配置して「現在アクティブ」と解釈しますか?

2番目のアプローチ(レコードごとに2つの日付列)ではなく、最初のアプローチ(1つの日付列)を選択した場合に直面する特定の問題はありますか?

目標

目的は、監査の目的と顧客の質問です。つまり、2015年1月にパートXに対して何を請求しましたか?どうやって見つけるのですか?

または、お客様が注文した昨年と比較して、なぜ200ドル多く請求するのですか?このため、またはそのため、12月に価格を変更しました。

どのように変更が生じるか-1月にパートXに100ドルを請求するように人事部が指示するが、2月からは200ドルを請求するとします。

5
Dennis

イベントソーシングを使用したドメインドリブンデザイン は、追記専用のログに記録される不変のエントリを使用してイベントを格納すると言います。発生したイベントのみを保存するため、イベントの日付は1つです(つまり、終了日はありません)。

ただし、おそらくすべての現在の価格も知りたいので、CQRS/ESは、それだけを格納する別のテーブルがどこかにあると言っています。 CQRS/ESの設計では、そのテーブルをキャッシュと呼び、破棄して自由に再構築できます。 (したがって、それらはキャッシュではなくログの強化に集中します。)

現在の価格を保存する場合、現在の価格を1つだけ保存するため、終了日情報は必要ありません。必要に応じて日付を含めることもできますが、古い価格に関する他の計算では、おそらくイベントストア(または他のキャッシュされた情報を保存)に戻る必要があります。

(ところで、イベントストアは同じSQLデータベースでも、別のものでもかまいません。)

これの利点は、不変の追記専用ログがさまざまなテクノロジー(SQL、NoSQL)で簡単であり、現在の日付のみを格納するキャッシュが非常にシンプルなテーブルであることです。

6
Erik Eidt

確かに、2番目のアプローチの方が扱いが簡単です。

  • 日付が1つしかない場合、つまり新しい価格の開始日である場合、特定の価格レコードが特定の日付の計算に使用するものであるかどうかはわかりません。
    • 常に複数のレコードを読み取って、ソートされた方法でアクセスして、使用する価格を見つける必要があります。
    • 現在の価格でも、将来の予定価格変更の記録がすでにある可能性があるため、確信が持てません。
  • 開始日と終了日を指定すると、ある程度の冗長性が得られます。しかし、適切な価格を使用するリレーショナルクエリを作成する方がはるかに簡単です。

2番目のアプローチを使用する場合、以前の価格に戻すには、まずビジネスの観点から分析する必要があります。

  • 古い価格についてですが、新しい日付以降ですか?この場合、通常どおり新しい価格レコードを追加するだけです
  • それはエラーであり、新しいレコードをキャンセルする必要がありますか?この場合、それはより複雑です:
  • 以前の価格レコードを見つけ、終了日をリセットして更新する必要があります。前のレコードの検索は、降順の価格レコードへの順次アクセスで使用することも、既知の終了日(消去する価格レコードの開始日を知っているため)を使用することもできます。
  • しかし、間違った価格を使用した保存済みの計算を見つけて更新する必要もあります
  • もちろん、一貫性を保つために、前のステップは単一のトランザクション内で実行する必要があります。

確定されていない終了日については、2つの学校があります。

  • 純粋主義者は、この情報が不明であることを示すためにフィールドを空白のままにします。
  • 実用主義者は「31.12.9999」を終了日として設定します。これはそれほどすばらしいことではありませんが、クエリの一貫性(常に存在する終了日)と柔軟性(並べ替えに開始日または終了日を使用)を可能にします。メジャーERPベンダーはこのアプローチを日常的に使用しているため、nullほど美しいとは言えないにしても、これは実証済みの手法であると私は言います。
5
Christophe

オプション1(1行に1つの日付)を使用して特定の日付(現在またはそれ以外)の価格を見つけるには、複数のレコードを順番に読み取る必要があるという点で@Christopheに同意します(データベースエンジンがそれを行うので、コードは行いません)。クエリは次のようになります。

select
    price
from
    price_history
where
    product_id = myProductID
    date <= myDate
order by date desc
limit 1;

ただし、このオプションには1つの利点があります:ギャップやオーバーラップを心配する必要はありません(以下を参照)。 しかし、時間を進めるために複数のレコードを再生してそれらを順次処理する必要があるため、期間を変更したい場合、大きな欠点があります

そのため、クエリは次のように単純なので、2番目のオプションの方が優れています

select
    price
from
    price_history
where
    product_id = myProductID and
    from_date <= myDate and
    ( thru_date >= MyDate or thru_date is null);

明らかに、実際の価格は、まだ有効期限がないため、THRU_DATE列にnullが含まれているはずです。

ただし、考慮すべき点がいくつかあります:

  • PRICE_HISTORYテーブルのPKは(PRODUCT_ID,FROM_DATE)である必要があります。または、PKを代理にする場合は、(PRODUCT_ID,FROM_DATE)に一意のインデックスが必要です。
  • ギャップを見つける:ギャップ、つまり価格のない期間を回避するために、データベースの制限を使用する方法はありません。そのため、トリガーまたはストアドプロシージャを使用して処理する必要があります。
  • 重複を見つける:上記と同じ、トリガーまたはSPを指定して、同じ製品の日付が重複しないようにする必要がありますこれは、priceのクエリで複数の行が生成される場合にエラーが発生するためです。
  • また、THRU_DATEにnullを含めることができるのは、製品ごとに1行のみです。トリガーまたはSP再度。

ボトムライン:

一部の人はオプション1(1行に1つの日付)を好むため、ギャップや重複を気にする必要はありませんが、2番目のオプションはより人間が読みやすいIMOです。

いずれの場合も、価格監査テーブルを追加して、誰がいつどの行を変更したかを反映します。 old_priceとnew_priceだけでなく、from_dateとthru_dateの変更も登録する場合は、PRICE_HISTORYテーブルのPKを代理にする必要があります。また、PRICE_HISTORYPRICE_AUDITを混同しないでください。 1つのテーブルには時間の経過に伴う製品の価格が含まれ、2番目のテーブルには前者のレコードに加えられた変更が保持されます。

AUDIT_TABLEおよびER図を含む、時間の経過に伴う価格の変化に関連する これらの他の回答 を参照してください。

1

ほとんどの人と同じように、オプション2を使用します。 FromDateとToDateの好ましい結果の1つは、ギャップを危険にさらすことなく、一時的な割引をエレガントに実装できることです。

Table Product_Price
Product_ID  Price  FromDate  ToDate
----------  -----  --------  --------
1           300    1/1/1900  Null
1           250    1/1/2016  8/1/2016

次に、関心のある日に有効なlowest価格を取得して、選択を行う必要があります

一時的な価格が必要な非常に奇妙な状況でない限り、その場合は複数の挿入を行う必要があります。

永続的な価格変更は、終了日を含む古い価格の更新と新しい価格の挿入を使用して実装されます。

1
mcottle

私の意見では、代わりに別のテーブルをItem_Pricingとして作成する必要があります。これは、価格設定履歴を確定するのに役立ちます。テーブルのItem_Pricing履歴は、このItem_Pricing(id [PK]、itm_id [FK]、price、price_still_in_use、date_price_created、...)のようになります。アイデアは、カラムprice_still_in_useは、値1(yes)または0(no)を持つことができるフラグと見なす必要があります。次に、テーブルでtransactionこれは考慮すべき価格です。次に、各トランザクションまたは販売は、引き続き使用されている価格設定に関連している必要があります。

トランザクション(id [PK]、...、item_pricing_id [FK] //これは、Item_Pricing(item_pricing_id)を指します、数量、日付)

したがって、アプリケーションでアイテムの価格が変更された場合、そのアイテムの価格は、使用されていなければ使用できなくなります。トリガーはまた、最良の結果を得るのに役立つ可能性があります。そのため、同じアイテムが、列(属性)price_still_in_useの値がyesである価格を複数持つことはできません。

0
Glodi-Natlor