Bien sûr, je me rends compte qu’il n’existe pas de "bonne manière" de concevoir une base de données SQL, mais je souhaitais obtenir quelques opinions sur ce qui est meilleur ou pire dans mon scénario particulier.
Actuellement, je conçois un module de saisie des commandes (application Windows .NET 4.0 avec SQL Server 2008) et je suis tiraillé entre deux décisions de conception en ce qui concerne les données pouvant être appliquées à plusieurs endroits. Dans cette question, je me référerai spécifiquement aux adresses.
Les adresses peuvent être utilisées par divers objets (commandes, clients, employés, livraisons, etc.) et contiennent presque toujours les mêmes données (Adresse1/2/3, Ville, État, Code postal, Pays, etc.). À l'origine, j'allais inclure chacun de ces champs sous forme de colonne dans chacune des tables associées (par exemple, les commandes contiendraient Adresse1/2/3, Ville, État, etc., et les clients contiendraient également la même présentation en colonnes). Mais une partie de moi veut appliquer les principes DRY/Normalisation à ce scénario, c’est-à-dire avoir un tableau appelé "Adresses" qui est référencé via une clé étrangère dans le tableau approprié.
CREATE TABLE DB.dbo.Addresses
(
Id INT
NOT NULL
IDENTITY(1, 1)
PRIMARY KEY
CHECK (Id > 0),
Address1 VARCHAR(120)
NOT NULL,
Address2 VARCHAR(120),
Address3 VARCHAR(120),
City VARCHAR(100)
NOT NULL,
State CHAR(2)
NOT NULL,
Country CHAR(2)
NOT NULL,
PostalCode VARCHAR(16)
NOT NULL
)
CREATE TABLE DB.dbo.Orders
(
Id INT
NOT NULL
IDENTITY(1000, 1)
PRIMARY KEY
CHECK (Id > 1000),
Address INT
CONSTRAINT fk_Orders_Address
FOREIGN KEY REFERENCES Addresses(Id)
CHECK (Address > 0)
NOT NULL,
-- other columns....
)
CREATE TABLE DB.dbo.Customers
(
Id INT
NOT NULL
IDENTITY(1000, 1)
PRIMARY KEY
CHECK (Id > 1000),
Address INT
CONSTRAINT fk_Customers_Address
FOREIGN KEY REFERENCES Addresses(Id)
CHECK (Address > 0)
NOT NULL,
-- other columns....
)
Du point de vue de la conception, cette approche me plaît, car elle crée un format d’adresse standard facilement modifiable, c’est-à-dire que si jamais j'avais besoin d’ajouter Address4, je l’ajouterais simplement à un endroit plutôt qu’à chaque table. Cependant, je peux voir que le nombre de jointures requises pour construire des requêtes peut devenir un peu fou.
Je suppose que je me demande simplement si des architectes SQL de niveau entreprise ont déjà utilisé cette approche avec succès, ou si le nombre de jointures que cela crée créerait un problème de performances.
Vous êtes sur la bonne voie en introduisant l'adresse dans sa propre table. J'aimerais ajouter quelques suggestions supplémentaires.
Envisagez de supprimer les colonnes Address FK des tables Customers/Orders et de créer des tables de jonction. En d'autres termes, traitez les clients/adresses et commandes/adresses comme des relations plusieurs à plusieurs dans votre conception maintenant afin de pouvoir prendre en charge plusieurs adresses à l'avenir. Oui, cela implique d’introduire plus de tables et de jointures, mais la flexibilité que vous gagnez en vaut la peine.
Envisagez de créer des tables de recherche pour les entités de ville, d'état et de pays. Les colonnes ville/état/pays de la table d'adresses sont alors constituées de FK pointant vers ces tables de recherche. Cela vous permet de garantir des orthographes cohérentes pour toutes les adresses et vous permet de stocker des métadonnées supplémentaires (par exemple, la population de la ville) si nécessaire à l'avenir.
J'ai juste quelques précautions. Pour chacun de ces problèmes, il existe plusieurs moyens de résoudre le problème.
Premièrement, normaliser ne signifie pas "remplacer le texte par un numéro d'identification".
Deuxièmement, vous n'avez pas de clé. Je sais que vous avez une colonne déclarée "PRIMARY KEY", mais cela ne suffit pas.
insert into Addresses
(Address1, Address2, Address3, City, State, Country, PostalCode)
values
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500'),
('President Obama', '1600 Pennsylvania Avenue NW', NULL, 'Washington', 'DC', 'US', '20500');
select * from Addresses;
1;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
2;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
3;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
4;President Obama;1600 Pennsylvania Avenue NW;;Washington;DC;US;20500
En l'absence de toute autre contrainte, votre "clé primaire" identifie une ligne; il n'identifie pas une adresse. Identifier une ligne n'est généralement pas suffisant.
Troisièmement, "Address1", "Address2" et "Address3" ne sont pas des attributs d'adresses. Ce sont des attributs des étiquettes de publipostage. (Lignes sur une étiquette de publipostage.) Cette distinction n’est peut-être pas importante pour vous. C'est vraiment important pour moi.
Quatrièmement, les adresses ont une durée de vie. Entre la naissance et la mort, ils changent parfois. Ils changent lorsque les rues sont réacheminées, les bâtiments divisés, les bâtiments indivisibles, et parfois (j'en suis presque sûr) lorsqu'un employé de la ville en a trop. Les catastrophes naturelles peuvent éliminer des communautés entières. Parfois, les bâtiments sont renumérotés. Dans notre base de données, qui est minuscule par rapport à la plupart, environ 1% par an change de la sorte.
Quand une adresse meurt, vous devez faire deux choses.
Quand une adresse change elle-même, vous devez faire deux choses.
Cinquièmement, DRY ne s'applique pas aux clés étrangères. Leur but est d'être répété. La seule question est quelle est la largeur de la clé? Un numéro d'identification est étroit, mais nécessite une jointure. (10 numéros d'identification peuvent nécessiter 10 jointures.) Une adresse est large, mais ne nécessite aucune jointure. (Je parle ici d'une adresse correcte, pas d'une étiquette d'adresse.)
C’est tout ce que je peux penser par cœur.
Je pense qu'il y a un problème dont vous n'êtes pas au courant, à savoir que certaines de ces données sont sensibles au temps. Vous ne voulez pas que vos dossiers montrent que vous avez expédié une commande au 35 King Street, Chicago Il, alors que vous l'avez envoyée au 10 King Street, Martinsburg WV, mais que le client a déménagé deux ans après l'expédition de la commande. Donc, oui, créez une table d'adresses pour obtenir l'adresse à ce moment-là, à condition que tout changement d'adresse d'une personne telle qu'un client entraîne la création d'une nouvelle adresse, qui ne modifiera pas l'adresse actuelle, ce qui romprait l'historique d'une commande.
Autant que je sache, il n’est pas utile d’avoir une table séparée pour les adresses. Cela conduira à plus de jointures et à un code plus compliqué, et votre phrase «contiennent presque toujours les mêmes données» me fait penser que vous allez trouver des exceptions douloureuses.
Vous voudriez que les adresses soient dans une table séparée si elles étaient des entités à part entière (ce qui signifie qu'elles avaient une identité et qu'il importait que deux objets pointent vers la même adresse ou vers des adresses différentes). Si c'était le cas avec votre domaine, je pense que cela serait totalement évident et que vous n'auriez pas besoin de poser cette question. Une autre réponse concernait également la mutabilité des adresses. Par exemple, une adresse de livraison fait partie d’une commande et ne devrait pas pouvoir être modifiée. L'adresse n'a donc pas son propre cycle de vie et sa gestion en tant qu'entité distincte ne peut que prêter à confusion.
La "normalisation" fait spécifiquement référence à la suppression des redondances dans les données afin que le même élément ne soit pas représenté à des endroits différents. Ici, la seule redondance est dans le DDL, ce n'est pas dans les données, donc la "normalisation" n'est pas pertinente ici.
Ce que vous devez répondre vous-même est la question de savoir si la même adresse dans le langage courant est en fait la même adresse dans votre base de données. Si quelqu'un "change d'adresse" (familièrement), il se lie réellement à une autre adresse. L'adresse en tant que telle ne change que lorsqu'une rue est renommée, qu'une réforme du code postal a lieu ou qu'une attaque nucléaire se produit. Et ce sont des événements rares (espérons-le pour la plupart). Voilà votre principal profit: changez au même endroit plusieurs rangées (de plusieurs tables).
Si vous devez en fait modifier une adresse dans votre modèle (au sens d'une adresse UPDATE
sur table), cette option peut ou non fonctionner pour les autres lignes qui y sont liées. De plus, selon mon expérience, même la même adresse exacte doit avoir une apparence différente pour des objectifs différents. Comprenez les différences sémantiques et vous arriverez au bon modèle qui représente le mieux votre monde réel.
J'ai un certain nombre de bases de données où j'utilise un tableau commun de rues (qui utilise un tableau de villes (qui utilise un tableau de pays, ...)). En combinaison avec un numéro de rue, considérez-le comme des géocodes (lat/lon) et non comme des "noms de rue". Les adresses sont pas / partagées entre différentes tables (ou lignes). Les modifications apportées aux noms de rue et aux codes postaux en cascade, les autres modifications ne le sont pas.
Normalement, vous normalisez les données autant que possible. Vous devez donc utiliser le tableau "Adresses".
Vous pouvez utiliser des vues pour dé-normaliser les données, lesquelles utilisent ensuite des index et devraient fournir une méthode pour accéder aux données avec des références simples, tout en laissant la structure sous-jacente entièrement normalisée.
Le nombre de jointures ne devrait pas être un problème majeur, les jointures basées sur un index ne sont pas trop lourdes.
C'est bien d'avoir une table d'adresses divisée.
Cependant, vous devez éviter de laisser plusieurs lignes se référer à la même adresse sans un système approprié de gestion des options permettant à l'utilisateur de décider si et comment la modification d'une adresse divise une ligne pour la nouvelle adresse. même adresse de facturation et de livraison. Ensuite, un utilisateur dit que son adresse change. Pour commencer, les anciennes commandes peuvent (devrait?) Avoir besoin de conserver leurs adresses de livraison, vous ne pouvez donc pas les modifier sur place. Mais l'utilisateur peut également avoir besoin de dire que l'adresse que je change ne changera que le destinataire.
Je préfère utiliser une table XREF contenant une référence FK à la table personne/entreprise, une référence FK à la table adresse et, en général, une référence FK à une table de rôle (HOME, OFFICE, etc.) pour définir le type adresse. J'inclus également un indicateur ACTIVE pour me permettre de choisir d'ignorer l'ancienne adresse tout en préservant la possibilité de conserver un historique d'adresses.
Cette approche me permet de gérer plusieurs adresses de types différents pour chaque entité principale.