Stockage d'une meilleure pratique d'adresse de facturation dans le tableau des commandes

10

Quelqu'un peut-il m'aider à comprendre la réponse de cet utilisateur pour une table CustomerLocation ? Je veux vraiment une bonne méthode pour stocker des adresses dans la table des commandes.

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.

En l'état, mon schéma ressemble à:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Communauté
la source

Réponses:

16

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:

Figure 1 - Diagramme IDEF1X du référentiel clients, commandes et adresses

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 Historytables

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 AddressHistoryet CustomerAddressHistoryaident à 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 Historytableau 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.CreatedDateTimeet AddressHistory.AuditedDateTimerepré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 CustomerAddressHistorylignes.

La CustomerAddress.IsActivecolonne BIT (booléen) est destinée à indiquer si une certaine Addressligne est "utilisable" par une Customerligne 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 Addresstable, 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 Addressligne individuelle DE son AddressHistory, vous devez prendre en compte le MAX(AddressHistory.AuditedDateTime)et le AddressHistory.AddressIdqui correspond à la Address.AddressIdvaleur 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 .

MDCCL
la source
1
Désolé, je sais que le message est plus ancien, mais pourquoi faites-vous référence (par exemple: REFERENCES Address (AddressId)) dans MyOrder? Pourquoi pas CustomerAddress?
Shadrix
1
Pas de soucis, et belle prise: En fait, les deux MyOrder.ShippingAddressIdet MyOrder.BillingAddressIddoivent 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.
MDCCL
2
@Shadrix Je viens de modifier le post, au cas où vous voudriez y jeter un œil.
MDCCL
@MDCCL Lorsque vous avez dit non UPDATE et DELETE sur la Historytable, devrait-il en être de même pour la Addresstable? 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 dans Historypuis faire une nouvelle insertion dans la Addresstable, non?
Mike Ross
1
OTOH, si un Client souhaite modifier une ou plusieurs informations sur une Adresse donnée , il faut s'assurer que (a) la Addressligne correspondante qui était «présente» jusqu'à ce que la modification ait eu lieu soit INSÉRÉE dans le AddressHistorytableau, et aussi que (b ) la Addressligne 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.
MDCCL
3

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é:

[vous devez] organiser la structure de votre base de données avec une table pour conserver les données liées à la commande et une autre table pour conserver les informations d'adresse. Et, oui, vous pouvez certainement avoir une table représentant une relation plusieurs-à-plusieurs entre ces deux types d'entités. Si un utilisateur peut changer ses attributs d'adresse (s), alors vous devez garder une trace de ces modifications, vous devez donc activer le correspondant AddressHistory. Ce poste est lié à ce dernier aspect.

MDCCL a également donné un aperçu sur la façon de trouver l'adresse actuelle d'un utilisateur ici:

Afin de récupérer la dernière version d'un tableau d'historique que vous avez, vous devez prendre en compte MAX(AuditedDateTime)le correspondant AddressId. La première étape consiste à modéliser / concevoir vos meilleurs arrangements conceptuels et logiques possibles, la deuxième étape consiste à trouver les moyens appropriés pour INSÉRER, METTRE À JOUR, SUPPRIMER et SÉLECTIONNER vos données.

Erik
la source