Si je comprends bien le scénario, vous devez définir une table qui conserve une série chronologique de prix ; par conséquent, je suis d'accord, cela a beaucoup à voir avec l' aspect temporel de la base de données avec laquelle vous travaillez.
Règles métier
Commençons par analyser la situation au niveau conceptuel. Donc, si , dans votre domaine d'activité,
- un produit est acheté à un à de nombreux prix ,
- chaque prix d'achat devient actuel à une date de début exacte , et
- le prix EndDate (qui indique la date où un prix cesse d'être actuelle ) est égale à la date de début de la immédiatement après le prix ,
alors cela signifie que
- il n'y a pas d' écart entre les périodes distinctes pendant lesquelles les prix sont courants (la série chronologique est continue ou conjointe ), et
- la EndDate d'un prix est une donnée dérivable.
Le diagramme IDEF1X illustré à la figure 1 , bien que très simplifié, illustre un tel scénario:
Disposition logique d'exposition
Et la conception de niveau logique SQL-DDL suivante, basée sur ledit diagramme IDEF1X, illustre une approche réalisable que vous pouvez adapter à vos propres besoins exacts:
-- 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)
);
Le Price
tableau a une CLÉ PRIMAIRE composite composée de deux colonnes, c'est-à-dire ProductNumber
(contrainte, à son tour, comme une CLÉ ÉTRANGÈRE qui fait référence à Product.ProductNumber
) et StartDate
(indiquant la Date particulière à laquelle un certain Produit a été acheté à un Prix spécifique ) .
Dans le cas où les produits sont achetés à des prix différents au cours du même jour , au lieu de la StartDate
colonne, vous pouvez en inclure un étiqueté comme StartDateTime
qui conserve l' instantané lorsqu'un produit donné a été acheté à un prix exact . La CLÉ PRIMAIRE devrait alors être déclarée comme (ProductNumber, StartDateTime)
.
Comme démontré, le tableau susmentionné est un tableau ordinaire, car vous pouvez déclarer des opérations SELECT, INSERT, UPDATE et DELETE pour manipuler directement ses données, ce qui (a) permet d'éviter l'installation de composants supplémentaires et (b) peut être utilisé dans tous les les principales plates-formes SQL avec quelques ajustements, si nécessaire.
Exemples de manipulation de données
Pour illustrer certaines opérations de manipulation qui semblent utiles, disons que vous avez INSÉRÉ respectivement les données suivantes dans les tableaux Product
et Price
:
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);
Étant donné que le Price.EndDate
est un point de données dérivable, vous devez alors l'obtenir via, précisément, une table dérivée qui peut être créée sous forme de vue afin de produire la série temporelle «complète», comme illustré ci-dessous:
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;
Ensuite, l'opération suivante qui sélectionne directement à partir de cette vue
SELECT ProductNumber,
ProductEtcetera,
PriceAmount,
StartDate,
EndDate
FROM PriceWithEndDate
ORDER BY StartDate DESC;
fournit le jeu de résultats suivant:
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.
Supposons maintenant que vous souhaitez obtenir l'intégralité des Price
données pour le Product
principal identifié par ProductNumber
1750 le Date
2 juin 2017 . Voyant qu'une Price
assertion (ou ligne) est actuelle ou effective pendant tout l' intervalle qui va de (i) son StartDate
à (ii) son EndDate
, alors cette opération 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.
donne l'ensemble de résultats qui suit
ProductNumber ProductEtcetera PriceAmount StartDate EndDate
------------- ------------------ ----------- ---------- ----------
1750 Price time series… 1000 2017-06-01 2017-06-03
qui répond à cette exigence.
Comme indiqué, la PriceWithEndDate
vue joue un rôle primordial dans l'obtention de la plupart des données dérivables, et peut être sélectionnée à partir d'une manière assez ordinaire.
Étant donné que votre plate-forme de préférence est PostgreSQL, ce contenu du site de documentation officiel contient des informations sur les vues «matérialisées» , qui peuvent aider à optimiser la vitesse d'exécution au moyen de mécanismes de niveau physique, si cet aspect devient problématique. D'autres systèmes de gestion de bases de données SQL (SGBD) offrent des instruments physiques très similaires, bien que des terminologies différentes puissent être appliquées, par exemple, des vues «indexées» dans Microsoft SQL Server.
Vous pouvez voir les exemples de code DDL et DML discutés en action dans ce violon db <> et dans ce violon SQL .
Ressources associées
Dans ce Q & A, nous discutons d'un contexte commercial qui inclut les changements de prix des produits mais a une portée plus étendue, vous pouvez donc le trouver intéressant.
Ces publications Stack Overflow couvrent des points très pertinents concernant le type de colonne contenant une donnée de devise dans PostgreSQL.
Réponses aux commentaires
Cela ressemble au travail que j'ai fait, mais j'ai trouvé qu'il était beaucoup plus pratique / efficace de travailler avec une table où un prix (dans ce cas) a une colonne startdate et une colonne enddate - vous recherchez donc simplement des lignes avec targetdate > = date de début et date cible <= date de fin. Bien sûr, si les données ne sont pas stockées avec ces champs (y compris la date de fin du 31 décembre 9999, pas Null, où aucune date de fin réelle n'existe), vous devrez alors travailler pour les produire. En fait, je l'ai fait fonctionner tous les jours, avec date de fin = date d'aujourd'hui par défaut. De plus, ma description requiert la date de fin 1 = la date de début 2 moins 1 jour. - @Robert Carnegie , le 2017-06-22 20: 56: 01Z
La méthode que je propose ci-dessus concerne un domaine d'activité des caractéristiques décrites précédemment , en appliquant par conséquent votre suggestion de déclarer le en EndDate
tant que colonne - qui est différente d'un «champ» - de la table de base nommée Price
impliquerait que la structure logique de la base de données serait ne pas refléter correctement le schéma conceptuel, et un schéma conceptuel doit être défini et reflété avec précision, y compris la différenciation (1) des informations de base des (2) informations dérivables .
En dehors de cela, une telle ligne de conduite introduirait une duplication, car elle EndDate
pourrait alors être obtenue en vertu de (a) une table dérivable et également en vertu de (b) la table de base nommée Price
, avec la EndDate
colonne donc dupliquée . Bien que cela soit une possibilité, si un praticien décide de suivre cette approche, il ou elle devrait décidément avertir les utilisateurs de la base de données des inconvénients et des inefficacités que cela implique. L'un de ces inconvénients et inefficacités est, par exemple, le besoin urgent de développer un mécanisme qui garantit, à tout moment , que chaque Price.EndDate
valeur est égale à celle de la Price.StartDate
colonne de la ligne immédiatement successive pour la Price.ProductNumber
valeur en question.
En revanche, le travail pour produire les données dérivées en question, comme je l'ai avancé, n'est honnêtement pas spécial du tout et est nécessaire pour (i) garantir la correspondance correcte entre les niveaux d'abstraction logiques et conceptuels de la base de données et (ii ) garantissent l'intégrité des données, deux aspects qui, comme indiqué précédemment, sont décidément d'une grande importance.
Si l'aspect d'efficacité dont vous parlez est lié à la vitesse d'exécution de certaines opérations de manipulation de données, alors il doit être géré à l'endroit approprié, c'est-à-dire au niveau physique, via, par exemple, une stratégie d'indexation avantageuse, basée sur (1 ) les tendances de requête particulières et (2) les mécanismes physiques particuliers fournis par le SGBD d'utilisation. Sinon, sacrifier le mappage conceptuel-logique approprié et compromettre l'intégrité des données impliquées transforme facilement un système robuste (c'est-à-dire un précieux actif organisationnel) en une ressource non fiable.
Séries chronologiques discontinues ou discontinues
D'un autre côté, il existe des circonstances dans lesquelles le maintien EndDate
de chaque ligne dans un tableau de séries chronologiques est non seulement plus pratique et efficace, mais aussi exigé , bien que cela dépende entièrement des exigences spécifiques à l'environnement commercial, bien sûr. Un exemple de ce genre de circonstances survient lorsque
- à la fois la date de début et les EndDate éléments d'information ont lieu avant (et conservés via) chaque insertion, et
- il peut y avoir des lacunes au milieu des périodes distinctes pendant lesquelles les prix sont courants (c'est-à-dire que la série chronologique est discontinue ou disjointe ).
J'ai représenté ledit scénario dans le diagramme IDEF1X illustré à la figure 2 .
Dans ce cas, oui, le Price
tableau hypothétique doit être déclaré d'une manière similaire à ceci:
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)
);
Et, oui, cette conception DDL logique simplifie l'administration au niveau physique, car vous pouvez mettre en place une stratégie d'indexation qui englobe la EndDate
colonne (qui, comme illustré, est déclarée dans une table de base) dans des configurations relativement plus faciles .
Ensuite, une opération SELECT comme celle ci-dessous
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';
peut être utilisé pour dériver l'ensemble des Price
données pour les Product
principales identifiées par ProductNumber
1750 le Date
2 juin 2017 .
prices
créer une tableprices_history
avec des colonnes similaires. Hibernate Envers peut automatiser cela pour vous