Selon votre description de l'environnement commercial considéré, il existe une structure de sous-type de supertype qui englobe l' élément —le supertype— et chacune de ses catégories , c'est-à-dire la voiture , le bateau et l' avion (ainsi que deux autres qui n'ont pas été révélées) - les sous-types—.
Je détaillerai ci-dessous la méthode que je suivrais pour gérer un tel scénario.
Règles métier
Afin de commencer à délimiter le schéma conceptuel pertinent , certaines des règles commerciales les plus importantes déterminées à ce jour (en limitant l'analyse aux trois catégories divulguées uniquement, pour que les choses soient aussi brèves que possible) peuvent être formulées comme suit:
- Un utilisateur possède zéro ou un ou plusieurs éléments
- Un article appartient à un seul utilisateur à un instant donné
- Un article peut appartenir à un ou plusieurs utilisateurs à des moments différents
- Un article est classé par exactement une catégorie
- Un article est, à tout moment,
- soit une voiture
- ou un bateau
- ou un avion
Diagramme illustratif IDEF1X
La figure 1 affiche un diagramme IDEF1X 1 que j'ai créé pour regrouper les formulations précédentes ainsi que d'autres règles métier qui semblent pertinentes:
Supertype
D'une part, Item , le supertype, présente les propriétés † ou attributs communs à toutes les Catégories , c.-à-d.
- CategoryCode —specified as a FOREIGN KEY (FK) qui fait référence à Category.CategoryCode et fonctionne comme un discriminateur de sous-type , c'est-à-dire qu'il indique la catégorie exacte de sous-type avec laquelle un élément donné doit être connecté—,
- OwnerId —distitué comme un FK qui pointe vers User.UserId , mais je lui ai attribué un nom de rôle 2 afin de refléter plus précisément ses implications spéciales—,
- Foo ,
- Bar ,
- Baz et
- CreatedDateTime .
Sous-types
D'un autre côté, les propriétés ‡ qui se rapportent à chaque catégorie particulière , c.-à-d.
- Qux et Corge ;
- Grault , Garply et Plugh ;
- Xyzzy , Thud , Wibble et Flob ;
sont affichés dans la boîte de sous-type correspondante.
Identifiants
Ensuite, la clé PRIMARY Item.ItemId (PK) a migré 3 vers les sous-types avec des noms de rôle différents, c'est-à-dire
- CarId ,
- BoatId et
- PlaneId .
Associations mutuellement exclusives
Comme illustré, il existe une association ou une relation de cardinalité un à un (1: 1) entre (a) chaque occurrence de supertype et (b) son instance de sous-type complémentaire.
Le symbole de sous-type exclusif décrit le fait que les sous-types s'excluent mutuellement, c'est-à-dire qu'une occurrence d' article concrète peut être complétée par une seule instance de sous-type uniquement: soit une voiture , soit un avion , soit un bateau (jamais par deux ou plus).
† , ‡ J'ai utilisé des noms d'espace réservé classiques pour autoriser certaines propriétés de type d'entité, car leurs dénominations réelles n'ont pas été fournies dans la question.
Disposition de niveau logique expositoire
Par conséquent, afin de discuter d'une conception logique expositoire, j'ai dérivé les instructions SQL-DDL suivantes basées sur le diagramme IDEF1X affiché et décrit ci-dessus:
-- 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 context.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATE NOT NULL,
GenderCode CHAR(3) NOT NULL,
Username CHAR(20) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);
CREATE TABLE Category (
CategoryCode CHAR(1) NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
Name CHAR(30) NOT NULL,
--
CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
CONSTRAINT Category_AK UNIQUE (Name) -- ALTERNATE KEY.
);
CREATE TABLE Item ( -- Stands for the supertype.
ItemId INT NOT NULL,
OwnerId INT NOT NULL,
CategoryCode CHAR(1) NOT NULL, -- Denotes the subtype discriminator.
Foo CHAR(30) NOT NULL,
Bar CHAR(30) NOT NULL,
Baz CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Item_PK PRIMARY KEY (ItemId),
CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
REFERENCES Category (CategoryCode),
CONSTRAINT Item_to_User_FK FOREIGN KEY (OwnerId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE Car ( -- Represents one of the subtypes.
CarId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Qux CHAR(30) NOT NULL,
Corge CHAR(30) NOT NULL,
--
CONSTRAINT Car_PK PRIMARY KEY (CarId),
CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
REFERENCES Item (ItemId)
);
CREATE TABLE Boat ( -- Stands for one of the subtypes.
BoatId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Grault CHAR(30) NOT NULL,
Garply CHAR(30) NOT NULL,
Plugh CHAR(30) NOT NULL,
--
CONSTRAINT Boat_PK PRIMARY KEY (BoatId),
CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
REFERENCES Item (ItemId)
);
CREATE TABLE Plane ( -- Denotes one of the subtypes.
PlaneId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Xyzzy CHAR(30) NOT NULL,
Thud CHAR(30) NOT NULL,
Wibble CHAR(30) NOT NULL,
Flob CHAR(30) NOT NULL,
--
CONSTRAINT Plane_PK PRIMARY KEY (PlaneId),
CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
REFERENCES Item (ItemId)
);
Comme démontré, le type de superentité et chacun des types de sous-entité sont représentés par la table de base correspondante .
Les colonnes CarId
, BoatId
et PlaneId
, comme les contraintes des tables PKs appropriées, aide à représenter l'un à une association niveau conceptuel par le biais de FK contraintes § ce point à la ItemId
colonne, qui est contrainte comme la pharmacocinétique de la Item
table. Cela signifie que, dans une «paire» réelle, les lignes de supertype et de sous-type sont identifiées par la même valeur PK; il est donc plus que opportun de mentionner que
- (a) attacher une colonne supplémentaire pour contenir les valeurs de substitution contrôlées par le système ‖ à (b) les tableaux représentant les sous-types est (c) entièrement superflu .
§ Afin d'éviter les problèmes et les erreurs concernant les définitions de contraintes KEY (en particulier ÉTRANGÈRES) - situation que vous avez mentionnée dans les commentaires -, il est très important de prendre en compte la dépendance à l' existence qui existe entre les différentes tables à portée de main, comme illustré dans l'ordre de déclaration des tables dans la structure DDL expositoire, que j'ai également fourni dans ce SQL Fiddle .
‖ Par exemple, ajouter une colonne supplémentaire avec la propriété AUTO_INCREMENT à une table d'une base de données construite sur MySQL.
Considérations d'intégrité et de cohérence
Il est essentiel de souligner que, dans votre environnement commercial, vous devez (1) vous assurer que chaque ligne de «supertype» est à tout moment complétée par son homologue «sous-type» correspondante et, à son tour, (2) garantir que ladite La ligne "sous-type" est compatible avec la valeur contenue dans la colonne "discriminateur" de la ligne "supertype".
Il serait très élégant d'appliquer de telles circonstances de manière déclarative mais, malheureusement, aucune des principales plates-formes SQL n'a fourni les mécanismes appropriés pour le faire, à ma connaissance. Par conséquent, le recours au code procédural dans ACID TRANSACTIONS est assez pratique pour que ces conditions soient toujours remplies dans votre base de données. Une autre option consisterait à utiliser TRIGGERS, mais ils ont tendance à rendre les choses désordonnées, pour ainsi dire.
Déclarer des vues utiles
Ayant une conception logique comme celle expliquée ci-dessus, il serait très pratique de créer une ou plusieurs vues, c'est-à-dire des tables dérivées qui comprennent des colonnes qui appartiennent à deux ou plusieurs des tables de base pertinentes . De cette façon, vous pouvez, par exemple, sélectionner directement à partir de ces vues sans avoir à écrire toutes les jointures à chaque fois que vous devez récupérer des informations «combinées».
Exemples de données
À cet égard, disons que les tables de base sont «remplies» avec les exemples de données ci-dessous:
--
INSERT INTO UserProfile
(UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
(1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
(2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
(3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());
INSERT INTO Category
(CategoryCode, Name)
VALUES
('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');
-- 1. ‘Full’ Car INSERTion
-- 1.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 1.2
INSERT INTO Car
(CarId, Qux, Corge)
VALUES
(1, 'Fantastic Car', 'Powerful engine pre-update!');
-- 2. ‘Full’ Boat INSERTion
-- 2.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 2.2
INSERT INTO Boat
(BoatId, Grault, Garply, Plugh)
VALUES
(2, 'Excellent boat', 'Use it to sail', 'Everyday!');
-- 3 ‘Full’ Plane INSERTion
-- 3.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 3.2
INSERT INTO Plane
(PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
(3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');
--
Ensuite, une vue avantageuse est une vue qui rassemble des colonnes de Item
, Car
et UserProfile
:
--
CREATE VIEW CarAndOwner AS
SELECT C.CarId,
I.Foo,
I.Bar,
I.Baz,
C.Qux,
C.Corge,
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Car C
ON C.CarId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Naturellement, une approche similaire peut être suivie afin que vous puissiez également sélectionner le «complet» Boat
et les Plane
informations directement à partir d' une seule table (une dérivée, dans ces cas).
Après que -si vous ne me dérange pas de la présence de marques de NULL dans le résultat ensembles- avec la définition VIEW suivante, vous pouvez, par exemple, « Collect » colonnes des tables Item
, Car
, Boat
, Plane
et UserProfile
:
--
CREATE VIEW FullItemAndOwner AS
SELECT I.ItemId,
I.Foo, -- Common to all Categories.
I.Bar, -- Common to all Categories.
I.Baz, -- Common to all Categories.
IC.Name AS Category,
C.Qux, -- Applies to Cars only.
C.Corge, -- Applies to Cars only.
--
B.Grault, -- Applies to Boats only.
B.Garply, -- Applies to Boats only.
B.Plugh, -- Applies to Boats only.
--
P.Xyzzy, -- Applies to Planes only.
P.Thud, -- Applies to Planes only.
P.Wibble, -- Applies to Planes only.
P.Flob, -- Applies to Planes only.
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Category IC
ON I.CategoryCode = IC.CategoryCode
LEFT JOIN Car C
ON C.CarId = I.ItemId
LEFT JOIN Boat B
ON B.BoatId = I.ItemId
LEFT JOIN Plane P
ON P.PlaneId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Le code des vues présentées ici n'est qu'illustratif. Bien sûr, faire des tests et des modifications pourrait aider à accélérer l'exécution (physique) des requêtes en cours. En outre, vous devrez peut-être supprimer ou ajouter des colonnes auxdites vues en fonction des besoins de l'entreprise.
Les exemples de données et toutes les définitions de vues sont incorporés dans ce SQL Fiddle afin qu'ils puissent être observés «en action».
Manipulation des données: code de programme (s) d'application et alias de colonne
L'utilisation du code des programmes d'application (si c'est ce que vous entendez par «code spécifique côté serveur») et des alias de colonne sont d'autres points importants que vous avez soulevés dans les commentaires suivants:
J'ai réussi à contourner un problème [JOIN] avec du code spécifique côté serveur, mais je ne veux vraiment pas que cela -et- l'ajout d'alias à toutes les colonnes soit "stressant".
Très bien expliqué, merci beaucoup. Cependant, comme je le soupçonnais, je devrai manipuler l'ensemble de résultats lors de la liste de toutes les données en raison des similitudes avec certaines colonnes, car je ne veux pas utiliser plusieurs alias pour garder la déclaration plus propre.
Il est opportun d'indiquer que l'utilisation du code de programme d'application est une ressource très appropriée pour gérer les fonctionnalités de présentation (ou graphiques) des jeux de résultats, éviter la récupération de données ligne par ligne est primordial pour éviter les problèmes de vitesse d'exécution. L'objectif devrait être de «récupérer» les ensembles de données pertinents au total au moyen des instruments de manipulation de données robustes fournis par le moteur de définition (précis) de la plate-forme SQL afin que vous puissiez optimiser le comportement de votre système.
En outre, l'utilisation d'alias pour renommer une ou plusieurs colonnes dans une certaine portée peut sembler stressante mais, personnellement, je considère cette ressource comme un outil très puissant qui aide à (i) contextualiser et (ii) lever l'ambiguïté du sens et de l' intention attribués au sujet concerné. Colonnes; par conséquent, c'est un aspect qui devrait être soigneusement réfléchi en ce qui concerne la manipulation des données d'intérêt.
Scénarios similaires
Vous pourriez aussi bien trouver utile cette série de messages et ce groupe de messages qui contiennent mon point de vue sur deux autres cas qui incluent des associations de sous-types avec des sous-types mutuellement exclusifs.
J'ai également proposé une solution pour un environnement commercial impliquant un cluster supertype-sous-type où les sous-types ne s'excluent pas mutuellement dans cette (plus récente) réponse .
Notes de fin
1 La définition d'intégration pour la modélisation de l'information ( IDEF1X ) est une technique de modélisation de données hautement recommandable qui a été établie en tant que norme en décembre 1993 par le National Institute of Standards and Technology (NIST)des États-Unis. Il est solidement basé sur (a) certains des travaux théoriques rédigés par le seul auteur du modèle relationnel , c'est-à-dire le Dr EF Codd ; sur (b) la vue entité-relation , développée par le Dr PP Chen ; et également sur (c) la technique de conception de bases de données logiques, créée par Robert G. Brown.
2 Dans IDEF1X, un nom de rôle est une étiquette distinctive attribuée à une propriété (ou attribut) FK afin d'exprimer la signification qu'il détient dans le cadre de son type d'entité respectif.
3 La norme IDEF1X définit la migration de clé comme «le processus de modélisation consistant à placer la clé primaire d'une entité parent ou générique dans son entité enfant ou catégorie comme clé étrangère».
Item
tableau comprend uneCategoryCode
colonne. Comme mentionné dans la section intitulée «Considérations d'intégrité et de cohérence»:CategoryColumn
dans les tableaux qui représentent les sous-types (avec toutes les implications aux niveaux logique [par exemple, anomalies de modification] et physique d'abstraction [par exemple, index supplémentaires, structures plus grandes, etc.]).Appelons la table principale Produits. Cela héberge les attributs partagés. Disons alors que nous avons une table Car, une table Plane et une table Boat. Ces trois tables auraient une clé ProductID avec une contrainte FK sur la ligne ID de la table Product. Si vous les voulez tous, rejoignez-les. Si vous voulez juste les voitures, joignez à gauche Cars with Products (ou joignez à droite les produits et les voitures, mais je préfère toujours utiliser les jointures à gauche).
C'est ce qu'on appelle un modèle de données hiearchical. Pour un petit nombre de sous-tableaux, cela peut avoir du sens dans un tableau long (millions de produits).
la source