「y」日付の「x」製品の価格を取得するために、データベースのすべての価格変更を追跡するにはどうすればよいですか


8

特定の日付で製品価格のデータベースを照会できるように、製品価格の変化を追跡する必要があります。この情報は、履歴監査を計算するシステムで使用されるため、購入日に基づいて、正しい製品の正しい価格を返す必要があります。

私はデータベースの構築にpostgresを使用したいと思います。

データベースの設計が必要ですが、あらゆるベストプラクティスの提案も歓迎します。


1
別のテーブルへの書き込み時にコピーします。テーブルの場合、同様の列をprices持つテーブルprices_historyを作成します。Hibernate Enversはこれを自動化できます
Neil McGuigan

回答:


11

シナリオを適切に理解している場合は、価格の時系列を保持するテーブルを定義する必要があります。したがって、私は同意します。これは、使用しているデータベースの一時的な側面に大きく関係しています。

ビジネスルール

状況を概念レベルから分析してみましょう。したがって、もしあなたのビジネスドメインで

  • 製品は、 1対多で購入された価格は
  • 正確なStartDateに各購入価格Currentになり、
  • 価格 終了日(示し価格でなくなっ電流が)に等しい開始日直後の価格

それはつまり

  • そこにはないギャップの異なる間の期間、その間価格がある現在は、(時系列がある連続または論理積)、および
  • PriceEndDateは導出可能なデータです。

IDEF1Xを示す図図1高度に簡略化されているがは、そのようなシナリオを示しています。

図1-簡略化されたIDEF1X図の製品価格-シナリオA

説明論理レイアウト

また、次のSQL-DDL論理レベル設計は、上記のIDEF1Xダイアグラムに基づいており、独自の正確なニーズに適応できる実現可能なアプローチを示しています。

-- At the physical level, you should define a convenient 
-- indexing strategy based on the data manipulation tendencies
-- so that you can supply an optimal execution speed of the
-- queries declared at the logical level; thus, some testing 
-- sessions with considerable data load should be carried out.

CREATE TABLE Product (
    ProductNumber INT      NOT NULL,
    Etcetera      CHAR(30) NOT NULL,
    --
    CONSTRAINT Product_PK PRIMARY KEY (ProductNumber)
);

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    Amount        INT  NOT NULL, -- Retains the amount in cents, but there are other options regarding the type of use.
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT AmountIsValid_CK    CHECK       (Amount >= 0)
);

Priceテーブルには、複合PRIMARY KEYは、2列、すなわちから成っていますProductNumber(を参照するFOREIGN KEYとして、順番に、制約Product.ProductNumber)とStartDate(特に指摘日付特定した製品は、特定の時に購入した価格) 。

製品が同じ日に異なる価格で購入された場合、列の代わりに、特定の製品が正確な価格で購入されたときにインスタントを保持するラベルが付いたものを含めることができます。次に、PRIMARY KEYをとして宣言する必要があります。StartDateStartDateTime(ProductNumber, StartDateTime)

示されているように、データを直接操作するSELECT、INSERT、UPDATE、およびDELETE操作を宣言できるため、前述のテーブルは通常のテーブルです。したがって、(a)追加コンポーネントのインストールを回避でき、(b)すべてで使用できます。必要に応じて、いくつかの調整を加えた主要なSQLプラットフォーム。

データ操作サンプル

有用と思われるいくつかの操作操作を例示するために、テーブルProductPriceテーブルにそれぞれ次のデータを挿入したとします。

INSERT INTO Product
    (ProductNumber, Etcetera)
VALUES
    (1750, 'Price time series sample'); 

INSERT INTO Price
    (ProductNumber, StartDate, Amount)
VALUES
    (1750, '20170601', 1000),
    (1750, '20170603', 3000),   
    (1750, '20170605', 4000),
    (1750, '20170607', 3000);

以来Price.EndDate導出データ点があり、その後、あなたはそれを経由して取得するためにしなければならず、正確には、派生として作成することができ、テーブルビューの下に例示されるような「フル」の時系列を生成するために:

CREATE VIEW PriceWithEndDate AS

    SELECT  P.ProductNumber,
            P.Etcetera AS ProductEtcetera,
           PR.Amount   AS PriceAmount,
           PR.StartDate,
           (
                SELECT MIN(StartDate)
                      FROM Price InnerPR
                     WHERE P.ProductNumber   = InnerPR.ProductNumber
                       AND InnerPR.StartDate > PR.StartDate
           ) AS EndDate
        FROM Product P
        JOIN Price   PR
          ON P.ProductNumber = PR.ProductNumber;

次に、そのビューから直接SELECTする次の操作

  SELECT ProductNumber,
         ProductEtcetera,
         PriceAmount,
         StartDate,
         EndDate
    FROM PriceWithEndDate 
ORDER BY StartDate DESC;

次の結果セットを提供します:

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         4000  2017-06-07  NULL      -- (*) 
         1750  Price time series         3000  2017-06-05  2017-06-07
         1750  Price time series         2000  2017-06-03  2017-06-05
         1750  Price time series         1000  2017-06-01  2017-06-03

-- (*) A ‘sentinel’ value would be useful to avoid the NULL marks.

今、私たちはあなたが全体得ることに興味を持っていると仮定しましょうPriceためのデータProductで、主に識別をProductNumber 1750年Date 2017年6月2日。ことを見Priceアサーション(又は行)が全体の中の電流または有効である区間(I)のから実行されることStartDateに(II)のEndDate、このDML操作

 SELECT ProductNumber,
        ProductEtcetera,
        PriceAmount,
        StartDate,
        EndDate
   FROM PriceWithEndDate
  WHERE ProductNumber = 1750        -- (1) 
    AND StartDate    <= '20170602'  -- (2)
    AND EndDate      >= '20170602'; -- (3)

-- (1), (2) and (3): You can supply parameters in place of fixed values to make the query more versatile.

次の結果セットを生成します

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         1000  2017-06-01  2017-06-03

上記の要件に対処します。

示されているように、PriceWithEndDateビューは、ほとんどの派生データを取得する上で最も重要な役割を果たし、かなり通常の方法でFROMから選択できます。

使用しているプラ​​ットフォームがPostgreSQLであることを考慮に入れて、公式ドキュメントサイトのこのコンテンツには、「マテリアライズド」ビューに関する情報が含まれています。他のSQLデータベース管理システム(DBMS)は、非常によく似た物理的な手段を提供しますが、Microsoft SQL Serverの「インデックス付き」ビューなど、異なる用語が適用される場合があります。

あなたは、アクションで議論DDLとDMLコードサンプルを見ることができます。このデシベル<>フィドルとで、このSQLフィドル

関連資料

  • ではこのQ&A私たちはの変更が含まビジネスコンテキスト話し合う製品価格をあなたが興味のそれを見つけることができて、より多くのextenseスコープを持っています。

  • これらのスタックオーバーフローのポストが保持している列の種類に関する非常に関連ポイントカバー通貨のPostgreSQL内のデータを。

コメントへの対応

これは私が行った作業に似ていますが、価格(この場合)に開始日列と終了日列があるテーブルを使用する方がはるかに便利/効率的であることがわかったので、targetdateを持つ行を探しているだけです。 > = startdateおよびtargetdate <= enddate。もちろん、データがこれらのフィールド(実際の終了日が存在しないNullではなく、9999年12月31日の終了日を含む)と共に格納されていない場合、データを生成するための作業を行う必要があります。実際には、デフォルトで終了日=今日の日付として毎日実行しました。また、私の説明には、終了日1 =開始日2-1日が必要です。– @Robert Carnegie2017年622日20:56:01Z

方法私はアドレスの上に特性の事業ドメインを提案前述の結果として、宣言についてのご提案を適用し、EndDate「フィールド」とは異なり-which列として-という名前のベーステーブルのをPrice暗示するデータベースの論理構造があろうと概念スキーマを正しく反映していない。概念スキーマは、(1)基本情報と(2)導出可能な情報の区別を含め、正確に定義および反映する必要がある。

それとは別に、そのような一連のアクションは重複を導入します。これはEndDate、(a)導出可能なテーブルと(b)という名前のベーステーブルPriceにより、重複したEndDate列を使用してを取得できるためです。これは可能性のあることですが、開業医が上記のアプローチに従うことを決定した場合、開業医はデータベースのユーザーにそれが伴う不便さと非効率性について明確に警告する必要があります。これらの不便さや非効率性の1つは、たとえば、各値が手元の値のすぐ次の行の列の値と常にPrice.EndDate等しいことを常に保証するメカニズムを開発する緊急の必要性です。Price.StartDatePrice.ProductNumber

対照的に、私が提起した問題の派生データを生成する作業は、正直なところ、まったく特別ではなく、(i)データベースの抽象化の論理レベルと概念レベルの間の正しい対応を保証する必要があり、(ii )データの整合性を確保します。前述のように、どちらの側面も明らかに非常に重要です。

あなたが話している効率性の側面が一部のデータ操作操作の実行速度に関連している場合、それは適切な場所、つまり物理レベルで、たとえば(1 )特定のクエリ傾向、および(2)使用するDBMSによって提供される特定の物理メカニズム。それ以外の場合、適切な概念論理マッピングを犠牲にして、関連するデータの整合性を損なうと、簡単に堅牢なシステム(つまり、貴重な組織資産)が信頼できないリソースに変わります。

不連続または分離した時系列

一方、EndDate時系列テーブルの各行のを保持する方が便利で効率的であるだけでなく、要求される場合もありますが、それはもちろんビジネス環境固有の要件に完全に依存します。そのような状況の一例は、

  • 両方の開始日終了日の情報片毎挿入前に保持された(とを介して保持される)、及び
  • そこにすることができギャップに異なるの途中で期間その間価格がある現在の(すなわち、時系列がある不連続または論理和)。

このシナリオは、図2に表示されているIDEF1Xダイアグラムで表しています

図2-製品価格の簡略化されたIDEF1X図-シナリオB

その場合、はい、仮想Priceテーブルは次のような方法で宣言する必要があります。

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    EndDate       DATE NOT NULL,
    Amount        INT  NOT NULL,
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate, EndDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT DatesOrder_CK       CHECK       (EndDate >= StartDate)
);

そして、はい、その論理DDL設計により、物理レベルでの管理が簡略化EndDateされます。これは、比較的簡単な構成で列(図に示されているように、ベーステーブルで宣言されている)を含むインデックス作成戦略を立てることができるためです。

次に、以下のようなSELECT操作

 SELECT  P.ProductNumber,
         P.Etcetera,
        PR.Amount,
        PR.StartDate,
        PR.EndDate
   FROM Price   PR
   JOIN Product P
  WHERE P.ProductNumber = 1750       
    AND StartDate      <= '20170602'  
    AND EndDate        >= '20170602';

2017年6月2日に1750年までに主に特定されたPriceデータ全体を導出するために使用できます。ProductProductNumber Date


これは私が行った作業に似ていますが、価格(この場合)に開始日列と終了日列があるテーブルを使用する方がはるかに便利/効率的であることがわかったので、targetdateを持つ行を探しているだけです。 > = startdateおよびtargetdate <= enddate。もちろん、データがこれらのフィールド(nullではなくenddate 31st Trumptember 9999を含み、実際の終了日が存在しない場合を含む)と一緒に保存されていない場合は、データを生成するための作業を行う必要があります。実際には、デフォルトで終了日=今日の日付として毎日実行しました。また、私の説明には、終了日1 =開始日2-1日が必要です。
ロバートカーネギー


1

ここでは、比較的単純でデータベースに特別な拡張を必要としない答えを出しました(したがって、どのデータベースでも機能します)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.