D'un point de vue conceptuel, bien que dans votre environnement commercial, l' ordre et l' adresse soient des idées étroitement associées, il s'agit en fait de deux types d'entités distincts, chacun avec son propre ensemble de propriétés (ou attributs) et de contraintes applicables.
Par conséquent, comme indiqué précédemment dans les commentaires, je suis d'accord avec @Erik et vous devez organiser la disposition logique de votre base de données en déclarant entre autres éléments:
- une table discrète pour conserver les informations de l' adresse ;
- un tableau pour conserver les détails spécifiques au client ;
- une table pour inclure les points de données de commande ; et
- un tableau contenant des faits sur les associations entre le (s) client (s) et l' adresse (les) ;
comme je vais illustrer ci-dessous.
Diagramme expositoire IDEF1X
Une image vaut mille mots, j'ai donc créé le diagramme IDEF1X illustré à la figure 1 pour illustrer certaines des possibilités ouvertes par ma suggestion:
Client , Adresse et leurs associations
Comme démontré, j'ai dépeint une association avec un rapport de cardinalité plusieurs-à-plusieurs (M: N) entre les types d'entité Client a et Adresse ; cette approche offrirait une flexibilité future car, comme vous le savez, un client peut conserver plusieurs adresses dans le temps, ou même simultanément, et la même adresse peut être partagée par plusieurs clients .
Une adresse particulière peut être utilisée de plusieurs manières par les clients un-à-plusieurs (1: M) ; par exemple, il peut être défini comme physique et / ou il peut être défini pour l' expédition et / ou pour la facturation . Peut-être, la même instance d' adresse peut servir à chacune des fins susmentionnées en même temps, ou elle peut couvrir deux utilisations tandis qu'une occurrence d' adresse différente couvre l'autre.
a Dans certains environnements commerciaux, un Client peut être soit une Personne soit une Organisation (situation qui impliquerait un arrangement légèrement distinct, comme détaillé dans cette réponse sur une structure supertype-sous-type) mais avec l'objectif de fournir un exemple simplifié, j'ai décidé de ne pas inclure cette possibilité ici. Dans le cas où vous devez couvrir cette situation dans votre base de données, le post du lien précédent montre la méthode pour résoudre cette exigence.
Ordre , Adresse , CustomerAddress et rôles Adresse
Généralement, une commande ne nécessite que deux types d' adresses , une pour l' expédition et une pour la facturation . De cette façon, la même instance d' adresse pourrait remplir les deux rôles pour une commande individuelle , mais chaque rôle est représenté par la propriété respective, à savoir ShippingAddressId ou BillingAddressId .
La commande est connectée à l' adresse via le type d'entité associative CustomerAddress à l'aide de deux clés étrangères multi-propriétés, c'est-à-dire
- ( CustomerNumber , ShippingAddressId ) et ( CustomerNumber , BillingAddressId ),
pointant tous les deux vers la CLÉ PRIMAIRE multi-propriétés CustomerAddress représentée par
- ( CustomerNumber , AddressId )
... qui aide à représenter une règle commerciale qui stipule que (a) une instance de Commande doit être liée exclusivement à (b) Les occurrences d' adresse précédemment associées au Client spécifique qui a passé cette Commande , et jamais à (c) un non Client aléatoire - Adresse associée .
Historique pour (1) Adresse et pour (2) l' association CustomerAddress
Si vous souhaitez fournir la possibilité de modifier les informations de l' adresse , vous devez suivre toutes les modifications des données. De cette manière, j'ai décrit l' Adresse comme un type d'entité «vérifiable» qui maintient sa propre AdresseHistorique .
Étant donné que la nature d'une connexion entre un client et une adresse peut également subir une ou plusieurs modifications, j'ai également décrit la possibilité de traiter une telle association comme une association «vérifiable» en vertu du type d'entité CustomerAddressHistory .
À cet égard, divers facteurs traités dans les questions et réponses no. 1 et Q & A no. 2 , à la fois sur l'activation des capacités temporelles dans une base de données, sont vraiment pertinentes.
Présentation logique illustrative SQL-DDL
Par conséquent, en termes de diagramme affiché et expliqué ci-dessus, j'ai déclaré la disposition de niveau logique suivante (que vous pouvez adapter pour répondre à vos besoins avec exactitude):
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define the
-- most convenient INDEX strategies based on the exact
-- data manipulation tendencies of your business domain.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE Customer (
CustomerNumber INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Customer_PK PRIMARY KEY (CustomerNumber)
);
CREATE TABLE Address (
AddressId INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Address_PK PRIMARY KEY (AddressId)
);
CREATE TABLE CustomerAddress (
CustomerNumber INT NOT NULL,
AddressId INT NOT NULL,
IsPhysical BIT NOT NULL,
IsShipping BIT NOT NULL,
IsBilling BIT NOT NULL,
IsActive BIT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT CustomerAddress_PK PRIMARY KEY (CustomerNumber, AddressId),
CONSTRAINT CustomerAddressToCustomer_FK FOREIGN KEY (CustomerNumber)
REFERENCES Customer (CustomerNumber),
CONSTRAINT CustomerAddressToAddress_FK FOREIGN KEY (AddressId)
REFERENCES Address (AddressId)
);
CREATE TABLE MyOrder (
CustomerNumber INT NOT NULL,
OrderNumber INT NOT NULL,
ShippingAddressId INT NOT NULL,
BillingAddressId INT NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
OrderDate DATE NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Order_PK PRIMARY KEY (CustomerNumber, OrderNumber),
CONSTRAINT OrderToCustomer_FK FOREIGN KEY (CustomerNumber)
REFERENCES Customer (CustomerNumber),
CONSTRAINT OrderToShippingAddress_FK FOREIGN KEY (CustomerNumber, ShippingAddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId),
CONSTRAINT OrderToBillingAddress_FK FOREIGN KEY (CustomerNumber, BillingAddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId)
);
CREATE TABLE AddressHistory (
AddressId INT NOT NULL,
AuditedDateTime DATETIME NOT NULL,
SpecificAttribute CHAR(30) NOT NULL,
ParticularAttribute CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT AddressHistory_PK PRIMARY KEY (AddressId, AuditedDateTime),
CONSTRAINT AddressHistoryToAddress_FK FOREIGN KEY (AddressId)
REFERENCES Address (AddressId)
);
CREATE TABLE CustomerAddressHistory (
CustomerNumber INT NOT NULL,
AddressId INT NOT NULL,
AuditedDateTime DATETIME NOT NULL,
IsPhysical BIT NOT NULL,
IsShipping BIT NOT NULL,
IsBilling BIT NOT NULL,
IsActive BIT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT CustomerAddressHistory_PK PRIMARY KEY (CustomerNumber, AddressId, AuditedDateTime),
CONSTRAINT CustomerAddressHistoryToCustomerAddress_FK FOREIGN KEY (CustomerNumber, AddressId)
REFERENCES CustomerAddress (CustomerNumber, AddressId)
);
Si vous voulez y jeter un œil, je l'ai testé dans ce violon db <> qui s'exécute sur SQL Server 2017.
Les History
tables
L'extrait suivant de votre question est très important:
Ce que je recherche, c'est comment configurer mes adresses, donc lorsque je les modifie, la commande n'est pas affectée par le fait qu'un client met à jour son adresse ou déménage.
Les tableaux AddressHistory
et CustomerAddressHistory
aident à garantir qu'un ordre n'est pas affecté par les changements d' adresse , car toutes les lignes «précédentes» doivent être conservées dans le History
tableau respectif et peuvent être interrogées si nécessaire. Les opérations UPDATE et DELETE sur ces deux tables doivent être interdites (essayer de changer l'historique peut même avoir des implications légales négatives).
L' intervalle englobe les valeurs incluses dans AddressHistory.CreatedDateTime
et AddressHistory.AuditedDateTime
représente la période entière pendant laquelle une certaine ligne «passée» a Address
été jugée «présente», «actuelle» ou «effective». Des considérations similaires s'appliquent aux CustomerAddressHistory
lignes.
La CustomerAddress.IsActive
colonne BIT (booléen) est destinée à indiquer si une certaine Address
ligne est "utilisable" par une Customer
ligne ou non; par exemple, si elle est définie sur "faux", cela signifierait qu'un client n'utilise plus cette adresse et qu'elle ne peut donc pas être utilisée pour de nouvelles commandes .
Remarque : D'un autre côté, j'ai vu certains systèmes dans lesquels chaque fois qu'une nouvelle commande est effectuée, les informations d' adresse doivent être saisies (plusieurs fois à plusieurs reprises), et la ou les adresses utilisées pour les commandes passées ne sont jamais effacées (d'où les commandes ne sont pas affectées par les changements d' adresse ).
Cette ligne de conduite peut indubitablement impliquer des volumes de redondance importants, mais il est possible que - selon les besoins d'informations exacts de votre domaine d'activité - puisse fonctionner, vous voudrez peut-être également évaluer ses avantages et ses inconvénients.
Récupération de données
La version «actuelle», «actuelle» ou «effective» d'une occurrence d' adresse doit être contenue comme une ligne dans la Address
table, mais la SÉLECTION des «états» précédents d'une adresse à partir de la table AddressHistory
(ou de CustomerAddressHistory
) est facile, et elle peut être un exercice intéressant pour améliorer vos compétences en codage SQL.
En ce qui concerne l'une des situations que vous avez mentionnées dans les commentaires, si vous souhaitez récupérer «l'avant-dernière version» d'une Address
ligne individuelle DE son AddressHistory
, vous devez prendre en compte le MAX(AddressHistory.AuditedDateTime)
et le AddressHistory.AddressId
qui correspond à la Address.AddressId
valeur particulière à portée de main.
À cet égard - au moins lors de la création d'une base de données relationnelle - , il est tout à fait pratique de définir d'abord le schéma conceptuel correspondant (basé sur les règles métier applicables ), puis de déclarer son arrangement DDL logique ultérieur . Une fois que vous obtenez des versions stables et fiables de ces éléments fondamentaux (qui, bien sûr, peuvent évoluer au fil du temps), il est temps d'analyser et de déterminer les meilleures façons de manipuler (via les opérations INSERT, UPDATE, DELETE et SELECT ou leurs combinaisons) la concernant les données.
Perception des utilisateurs finaux, vues et assistance pour les programmes d'application
De toute évidence, au niveau externe de l'abstraction, les informations d' adresse sont perçues (par les utilisateurs finaux) comme faisant partie d'un ordre , et il n'y a rien de mal à cela, mais cela ne signifie pas que les modélisateurs doivent concevoir les parties importantes de la base de données en question comme ça. Sur ce point, s'il y a la nécessité de, par exemple, imprimer une « complète » ordre (très possible), vous pouvez « Reproduire » sur demande avec l'aide de quelques opérateurs de jointure et les clauses WHERE (compte tenu de la concernant la période de validité , etc.) peut être corrigé dans les vues pour une consommation future, en envoyant le jeu de résultats pertinent au (x) programme (s) d'application associé (s) qui, à son tour, peut améliorer sa mise en forme si nécessaire.
Bien entendu, le (s) programme (s) d'application sera également très utile lors de l' exécution d' une Commande ; Par exemple, une fenêtre d'application de bureau / mobile ou une page Web peut:
- afficher uniquement la ou les adresses que le client concerné a établies comme «utilisables» (via
CustomerAddress.IsActive
);
- répertorier toutes les adresses que le client a activées pour le service de facturation (via
CustomerAddress.IsBilling
); et
- regrouper toutes les adresses que le client a définies pour le service d'expédition (via
CustomerAddress.IsShipping
);
faciliter de cette manière tous les processus impliqués dans l'interface graphique (c'est-à-dire le niveau externe d'abstraction d'un système informatisé).
Lecture suggérée
Vous avez demandé (dans les commentaires maintenant supprimés) quelques conseils sur la documentation de base de données solide; Par conséquent, en ce qui concerne le matériel théorique , je vous conseille vivement de lire tous les travaux écrits par le Dr EF Codd , récipiendaire du prix Turing et, bien sûr, seul créateur du modèle relationnel de données (peut-être maintenant plus pertinent que jamais). Cette liste comprend certains de ses articles et articles extrêmement influents.
Deux ouvrages importants qui ne figurent pas dans la liste susmentionnée sont, précisément, sa conférence du prix ACM Turing intitulée Relational Database: A Practical Foundation for Productivity , de 1981, et son livre intitulé The Relational Model for Database Management: Version 2 , qui a été publié. en 1990.
Sur le plan de la conception, la définition intégrée pour la modélisation de l'information (IDEF1X) est une technique sérieusement recommandable qui a été définie comme norme en décembre 1993 par le National Institute of Standards and Technology (NIST) des États-Unis .
MyOrder.ShippingAddressId
etMyOrder.BillingAddressId
doivent faire référence àCustomerAddress.AddressId
(et non àAddress.AddressId
); de cette manière, on garantit qu'une Commande peut être exclusivement associée à la ou aux Adresse (s) précédemment connectée (s) au Client qui a effectué cette Commande . Le diagramme suggère cet arrangement, donc le DDL sera plus précis. Merci d'avoir demandé cette clarification.History
table, devrait-il en être de même pour laAddress
table? Que se passe-t-il si le client commande quelque chose puis modifie uniquement le code postal ou la ville dans un seul champ. Nous devons insérer l'adresse existante dansHistory
puis faire une nouvelle insertion dans laAddress
table, non?Address
ligne correspondante qui était «présente» jusqu'à ce que la modification ait eu lieu soit INSÉRÉE dans leAddressHistory
tableau, et aussi que (b ) laAddress
ligne en question est MISE À JOUR avec la ou les nouvelles valeurs. Il serait avantageux d'effectuer ce processus comme une seule unité de travail dans une transaction.Cette réponse a été compilée à partir des commentaires à la question.
Une solution serait d'utiliser un FK pour la table d'adresses dans la table de commande. Cela vous permettra de voir les adresses qui ont été utilisées pour la commande et de dissocier l'adresse de l'adresse actuelle de l'utilisateur.
Pour que cela fonctionne, vous devez insérer une nouvelle adresse et lier cette nouvelle adresse à la table User. Cela signifie que les adresses sont écrites une seule fois et que la modification est une illusion pour l'utilisateur final. Vous pouvez stocker efficacement l'historique de toutes les adresses auxquelles un utilisateur a été associé en déplaçant l'association de la table User vers une table d'association avec un horodatage. Cela vous donnerait un historique des modifications / adresses et conserverait des données immuables dans la table des adresses.
@MDCCL a déclaré:
MDCCL a également donné un aperçu sur la façon de trouver l'adresse actuelle d'un utilisateur ici:
la source