Wie würde ich alle Preisänderungen in einer Datenbank verfolgen, um den Preis des 'x'-Produkts am' y'-Datum zu erhalten?

8

Ich müsste Änderungen des Produktpreises nachverfolgen, damit ich die Datenbank nach einem Produktpreis zu einem bestimmten Datum abfragen kann. Die Informationen werden in einem System verwendet, das historische Audits berechnet, sodass der richtige Preis für das richtige Produkt basierend auf dem Kaufdatum zurückgegeben werden muss.

Ich würde es vorziehen, Postgres beim Aufbau der Datenbank zu verwenden.

Ich brauche mit dem Design der Datenbank, aber alle Best-Practice-Vorschläge sind auch willkommen.

Gunnar Norred
quelle
1
Kopieren beim Schreiben in eine andere Tabelle. Wenn Tabelle ist, priceserstellen Sie eine Tabelle prices_historymit ähnlichen Spalten. Hibernate Envers kann dies für Sie automatisieren
Neil McGuigan

Antworten:

11

Wenn ich das Szenario angemessen verstehe, sollten Sie eine Tabelle definieren, die eine Preiszeitreihe enthält . Daher stimme ich zu, dass dies viel mit dem zeitlichen Aspekt der Datenbank zu tun hat, mit der Sie arbeiten.

Geschäftsregeln

Beginnen wir mit der Analyse der Situation auf konzeptioneller Ebene. Also, wenn in Ihrer Geschäftsdomäne,

  • Ein Produkt wird zu einem zu vielen Preisen gekauft .
  • jeder Preis des Kaufs wird Strom an einem exakten Startdatum und
  • der Preis EndDate (die die angibt , Datum , wenn ein Preis zu sein aufhört Strom ) ist gleich dem Startdatum des unmittelbar nachfolgenden Preis ,

dann bedeutet das das

  • es gibt keine Lücken zwischen den verschiedenen Perioden , in denen die Preise sind aktuelle (die Zeitreihe ist kontinuierlich oder in Konjunktion ) und
  • Das Enddatum eines Preises ist ein ableitbares Datum.

Das in Abbildung 1 gezeigte IDEF1X- Diagramm zeigt ein solches Szenario, obwohl es stark vereinfacht ist:

Abb. 1 - Produktpreise Vereinfachtes IDEF1X-Diagramm - Szenario A.

Logisches Layout des Expositorys

Das folgende Design auf logischer SQL-DDL-Ebene, das auf dem IDEF1X-Diagramm basiert, zeigt einen praktikablen Ansatz, den Sie genau an Ihre eigenen Anforderungen anpassen können:

-- 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)
);

Die PriceTabelle enthält einen zusammengesetzten PRIMARY KEY, der aus zwei Spalten besteht, dh ProductNumber(wiederum eingeschränkt als FOREIGN KEY, auf den verwiesen wird Product.ProductNumber) und StartDate(unter Angabe des bestimmten Datums, an dem ein bestimmtes Produkt zu einem bestimmten Preis gekauft wurde ). .

Für den Fall , dass Produkte an unterschiedlichen gekauft Preisen im gleichen Tag statt der StartDateSpalte können Sie einen als gekennzeichnet sind , StartDateTimedass hält die Sofort , wenn ein bestimmte Artikel in einem exakten erworben wurde Preis . Der PRIMARY KEY müsste dann als deklariert werden (ProductNumber, StartDateTime).

Wie gezeigt, handelt es sich bei der oben genannten Tabelle um eine gewöhnliche Tabelle, da Sie die Operationen SELECT, INSERT, UPDATE und DELETE deklarieren können, um ihre Daten direkt zu bearbeiten. Daher (a) kann die Installation zusätzlicher Komponenten vermieden werden, und (b) kann in allen verwendet werden die wichtigsten SQL-Plattformen mit einigen Anpassungen, falls erforderlich.

Datenmanipulationsbeispiele

Um einige Manipulationen exemplifizieren , die nützlich erscheinen, lassen Sie uns sagen , dass Sie die folgenden Daten in den eingefügt haben Productund PriceTabellen dargestellt:

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);

Da es sich bei dem Price.EndDateum einen ableitbaren Datenpunkt handelt, müssen Sie ihn genau über eine abgeleitete Tabelle abrufen, die als Ansicht erstellt werden kann, um die „vollständige“ Zeitreihe zu erstellen, wie im Folgenden veranschaulicht:

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;

Dann die folgende Operation, die direkt aus dieser Ansicht auswählt

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

liefert die nächste Ergebnismenge:

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.

Nehmen wir nun an, dass Sie daran interessiert sind, die gesamten PriceDaten für die Productprimär identifizierten bis ProductNumber 1750 am Date 2. Juni 2017 zu erhalten . Wenn Sie sehen, dass eine PriceZusicherung (oder Zeile) während des gesamten Intervalls , das von (i) StartDatebis (ii) bis zu (ii) läuft, aktuell oder wirksam ist EndDate, dann diese DML-Operation

 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.

ergibt die folgende Ergebnismenge

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

die diese Anforderung adressiert.

Wie gezeigt, PriceWithEndDatespielt die Ansicht eine entscheidende Rolle beim Abrufen der meisten ableitbaren Daten und kann auf ziemlich gewöhnliche Weise AUSGEWÄHLT werden.

Unter Berücksichtigung der Tatsache, dass Ihre bevorzugte Plattform PostgreSQL ist, enthält dieser Inhalt der offiziellen Dokumentationsseite Informationen zu „materialisierten“ Ansichten , die dazu beitragen können, die Ausführungsgeschwindigkeit mithilfe von Mechanismen auf physikalischer Ebene zu optimieren, falls dieser Aspekt problematisch wird. Andere SQL-Datenbankverwaltungssysteme (DBMS) bieten physische Instrumente, die sich sehr ähnlich sind, obwohl möglicherweise andere Begriffe verwendet werden, z. B. "indizierte" Ansichten in Microsoft SQL Server.

Sie können die besprochenen DDL- und DML-Codebeispiele in Aktion in dieser Datenbank-Geige und in dieser SQL-Geige sehen .

Ähnliche Resourcen

  • In diesen Fragen und Antworten wird ein Geschäftskontext erörtert , der die Änderungen der Produktpreise umfasst, jedoch einen größeren Umfang aufweist, sodass Sie ihn möglicherweise von Interesse finden.

  • Diese Stapelüberlauf-Posts decken sehr relevante Punkte in Bezug auf den Typ einer Spalte ab, die ein Währungsdatum in PostgreSQL enthält.

Antworten auf Kommentare

Dies sieht ähnlich aus wie meine Arbeit, aber ich fand es viel bequemer / effizienter, mit einer Tabelle zu arbeiten, in der ein Preis (in diesem Fall) eine Startdatums- und eine Enddatumspalte enthält. Sie suchen also nur nach Zeilen mit Zieldatum > = Startdatum und Zieldatum <= Enddatum. Wenn die Daten nicht in diesen Feldern gespeichert sind (einschließlich Enddatum 31. Dezember 9999, nicht Null, wo kein tatsächliches Enddatum vorhanden ist), müssen Sie natürlich arbeiten, um sie zu erstellen. Ich habe es tatsächlich jeden Tag laufen lassen, standardmäßig mit Enddatum = heutigem Datum. Außerdem erfordert meine Beschreibung Enddatum 1 = Startdatum 2 minus 1 Tag. - @ Robert Carnegie , am 2017-06-22 20: 56: 01Z

Die oben vorgeschlagene Methode adressiert eine Geschäftsdomäne mit den zuvor beschriebenen Merkmalen und wendet folglich Ihren Vorschlag an, die EndDateSpalte als Tabelle zu deklarieren, die sich von einem „Feld“ unterscheidet. Dies Pricewürde bedeuten, dass die logische Struktur der Datenbank dies tun würde nicht korrekt sein reflektieren des konzeptionelle Schemas und ein konzeptionelles Schema muss definiert und mit Präzision reflektiert werden, einschließlich der Differenzierung von (1) Basisinformationen aus (2) ableitbaren Informationen.

Abgesehen davon würde eine solche Vorgehensweise zu einer Vervielfältigung führen, da die EndDatedann aufgrund (a) einer ableitbaren Tabelle und auch aufgrund (b) der genannten Basistabelle Pricemit der daher duplizierten EndDateSpalte erhalten werden könnte. Während dies eine Möglichkeit ist, sollte ein Praktiker, wenn er sich für diesen Ansatz entscheidet, die Datenbankbenutzer entschieden vor den damit verbundenen Unannehmlichkeiten und Ineffizienzen warnen. Eine dieser Unannehmlichkeiten und Ineffizienzen ist beispielsweise die dringende Notwendigkeit, einen Mechanismus zu entwickeln, der jederzeit sicherstellt, dass jeder Price.EndDateWert gleich dem der Price.StartDateSpalte der unmittelbar aufeinanderfolgenden Zeile für den Price.ProductNumbervorliegenden Wert ist.

Im Gegensatz dazu ist die Arbeit zur Erstellung der fraglichen abgeleiteten Daten, wie ich sie dargelegt habe, ehrlich gesagt überhaupt nicht speziell und muss (i) die korrekte Entsprechung zwischen den logischen und konzeptuellen Abstraktionsebenen der Datenbank gewährleisten und (ii) ) Gewährleistung der Datenintegrität, wobei beide Aspekte, wie bereits erwähnt, ausgesprochen wichtig sind.

Wenn der Effizienzaspekt, von dem Sie sprechen, mit der Ausführungsgeschwindigkeit einiger Datenmanipulationsvorgänge zusammenhängt, muss er an der geeigneten Stelle verwaltet werden, dh auf physischer Ebene, z. B. über eine vorteilhafte Indizierungsstrategie, die auf (1) basiert ) die besonderen Abfragetendenzen und (2) die besonderen physikalischen Mechanismen, die vom DBMS verwendet werden. Andernfalls wird ein robustes System (dh ein wertvolles organisatorisches Gut) leicht zu einer nicht zuverlässigen Ressource, wenn die entsprechende konzeptionell-logische Zuordnung geopfert und die Integrität der beteiligten Daten beeinträchtigt wird.

Diskontinuierliche oder disjunkte Zeitreihen

Andererseits gibt es Umstände, unter denen das Beibehalten der EndDateeinzelnen Zeilen in einer Zeitreihentabelle nicht nur bequemer und effizienter ist, sondern auch gefordert wird , obwohl dies natürlich ausschließlich von den geschäftsumgebungsspezifischen Anforderungen abhängt. Ein Beispiel für diese Art von Umständen ergibt sich, wenn

  • Sowohl die StartDate- als auch die EndDate- Informationen werden vor jeder INSERTion gespeichert (und über diese gespeichert)
  • es kann Lücken in der Mitte der verschiedenen Perioden , in denen die Preise sind Strom (dh die Zeitreihe diskontinuierlich oder disjunct ).

Ich habe dieses Szenario in dem in Abbildung 2 gezeigten IDEF1X-Diagramm dargestellt .

Abb. 2 - Produktpreise Vereinfachtes IDEF1X-Diagramm - Szenario B.

In diesem Fall Pricemuss die hypothetische Tabelle auf ähnliche Weise deklariert werden:

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)
);

Und ja, dieses logische DDL-Design vereinfacht die Verwaltung auf physischer Ebene, da Sie eine Indexierungsstrategie erstellen können, die die EndDateSpalte (die, wie gezeigt, in einer Basistabelle deklariert ist) in relativ einfacheren Konfigurationen umfasst.

Dann eine SELECT-Operation wie die folgende

 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';

kann verwendet werden, um die gesamten PriceDaten für die Producthauptsächlich bis ProductNumber 1750 am Date 2. Juni 2017 identifizierten abzuleiten .

MDCCL
quelle
Dies sieht ähnlich aus wie meine Arbeit, aber ich fand es viel bequemer / effizienter, mit einer Tabelle zu arbeiten, in der ein Preis (in diesem Fall) eine Startdatums- und eine Enddatumspalte enthält. Sie suchen also nur nach Zeilen mit Zieldatum > = Startdatum und Zieldatum <= Enddatum. Wenn die Daten nicht in diesen Feldern gespeichert sind (einschließlich Enddatum 31. Trumptember 9999, nicht Null, wo kein tatsächliches Enddatum vorhanden ist), müssen Sie natürlich arbeiten, um sie zu erstellen. Ich habe es tatsächlich jeden Tag laufen lassen, standardmäßig mit Enddatum = heutigem Datum. Außerdem erfordert meine Beschreibung Enddatum 1 = Startdatum 2 minus 1 Tag.
Robert Carnegie
4

Ich glaube, Sie werden sich Temporal Tables ansehen wollen . Diese bieten Funktionen, mit denen Sie genau das tun können, wonach Sie suchen, und sind in Postgres mit den richtigen Erweiterungen verfügbar.

Dieses Konzept scheint auch ziemlich DB-unabhängig zu sein, da es auf einer Vielzahl von RDBMS-Plattformen angeboten wird .

John Eisbrener
quelle
1

Ich habe hier eine Antwort gegeben , die relativ einfach ist und keine speziellen Erweiterungen der Datenbank erfordert (daher funktioniert sie mit jeder Datenbank).

TommCatt
quelle