Secondo la tua descrizione dell'ambiente di business in esame, esiste una struttura di sottotipo di sottotipo che comprende Item - il supertipo - e ciascuna delle sue categorie , ovvero auto , barca e aereo (insieme ad altre due che non sono state rese note) - i sottotipi—.
Descriverò in dettaglio il metodo che seguirò per gestire un simile scenario.
Regole di business
Per iniziare a delineare lo schema concettuale pertinente , alcune delle più importanti regole commerciali finora determinate (limitando l'analisi alle sole tre categorie divulgate , per rendere le cose il più brevi possibile) possono essere formulate come segue:
- Un utente possiede zero-uno-o-molti articoli
- Un articolo è di proprietà di un solo utente in un preciso istante
- Un articolo può essere di proprietà di uno-a-molti utenti in momenti distinti nel tempo
- Un articolo è classificato esattamente per una categoria
- Un articolo è, in ogni momento,
- o un'auto
- o una barca
- o un aereo
Diagramma illustrativo IDEF1X
La Figura 1 mostra un diagramma IDEF1X 1 che ho creato per raggruppare le formulazioni precedenti insieme ad altre regole aziendali che sembrano pertinenti:
Supertype
Da un lato, Item , il supertipo, presenta le proprietà † o gli attributi comuni a tutte le categorie , ovvero
- CategoryCode: specificato come FOREIGN KEY (FK) che fa riferimento a Category.CategoryCode e funge da discriminatore del sottotipo , ovvero indica l'esatta Categoria del sottotipo con cui un determinato Articolo deve essere collegato—,
- OwnerId —distinto come un FK che punta a User.UserId , ma gli ho assegnato un nome di ruolo 2 per riflettere le sue speciali implicazioni in modo più preciso—,
- Foo ,
- Bar ,
- Baz e
- CreatedDateTime .
sottotipi
D'altra parte, le proprietà ‡ che appartengono a ogni particolare Categoria , ovvero
- Qux e Corge ;
- Grault , Garply e Plugh ;
- Xyzzy , Thud , Wibble e Flob ;
sono visualizzati nella casella del sottotipo corrispondente.
Identifiers
Quindi, Item.ItemId PRIMARY KEY (PK) ha migrato 3 nei sottotipi con nomi di ruoli diversi, ad esempio,
- CarId ,
- BoatId e
- PlaneId .
Associazioni reciprocamente esclusive
Come illustrato, esiste un'associazione o relazione di cardinalità one-to-one (1: 1) tra (a) ciascuna occorrenza di supertipo e (b) la sua istanza di sottotipo complementare.
Il simbolo esclusivo del sottotipo raffigura il fatto che i sottotipi si escludono a vicenda, vale a dire che un'occorrenza concreta dell'oggetto può essere integrata da una sola istanza del sottotipo: un'automobile o un aereo o una barca (mai di due o più).
† , ‡ Ho utilizzato i nomi di segnaposto classici per autorizzare alcune proprietà del tipo di entità, poiché le loro denominazioni effettive non sono state fornite nella domanda.
Layout a livello logico dell'esposizione
Di conseguenza, al fine di discutere una progettazione logica espositiva, ho derivato le seguenti istruzioni SQL-DDL basate sul diagramma IDEF1X visualizzato e descritto sopra:
-- 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)
);
Come dimostrato, il tipo di superentity e ciascuno dei tipi di subentity sono rappresentati dalla tabella di base corrispondente .
Le colonne CarId
, BoatId
e PlaneId
, vincolati come PK delle tabelle appropriate, aiuto nel rappresentare il nesso logico-livello uno-a-uno mediante vincoli FK § che scegliere la ItemId
colonna, che viene vincolata come PK della Item
tabella. Ciò significa che, in una vera "coppia", sia le righe del supertipo che quelle del sottotipo sono identificate dallo stesso valore PK; quindi, è più che opportuno menzionarlo
- (a) collegare una colonna aggiuntiva per contenere i valori surrogati controllati dal sistema da ‖ a (b) le tabelle che rappresentano i sottotipi sono (c) del tutto superflue .
§ Al fine di prevenire problemi ed errori relativi alle definizioni dei vincoli CHIAVE (in particolare ESTERI) —situazione a cui si fa riferimento nei commenti —, è molto importante tenere conto della dipendenza dall'esistenza che si verifica tra le diverse tabelle in questione, come esemplificato in l'ordine di dichiarazione delle tabelle nella struttura DDL espositiva, che ho fornito anche in questo SQL Fiddle .
‖ Ad esempio, aggiungere una colonna aggiuntiva con la proprietà AUTO_INCREMENT a una tabella di un database basato su MySQL.
Considerazioni di integrità e coerenza
È fondamentale sottolineare che, nel proprio ambiente aziendale, è necessario (1) assicurarsi che ogni riga del “sottotipo” sia sempre integrata dalla corrispondente controparte del “sottotipo” e, a sua volta, (2) garantire che detto La riga "sottotipo" è compatibile con il valore contenuto nella colonna "discriminatore" della riga "supertipo".
Sarebbe molto elegante applicare tali circostanze in modo dichiarativo ma, sfortunatamente, nessuna delle principali piattaforme SQL ha fornito i meccanismi adeguati per farlo, per quanto ne so. Pertanto, ricorrere al codice procedurale all'interno di TRANSAZIONI ACIDI è abbastanza conveniente in modo che queste condizioni siano sempre soddisfatte nel database. Un'altra opzione sarebbe impiegare TRIGGERS, ma tendono a rendere le cose disordinate, per così dire.
Dichiarazione di viste utili
Avendo una progettazione logica come quella spiegata sopra, sarebbe molto pratico creare una o più viste, ovvero tabelle derivate che comprendono colonne che appartengono a due o più delle tabelle di base pertinenti . In questo modo, ad esempio, è possibile SELEZIONARE direttamente DA tali viste senza dover scrivere tutti i JOIN ogni volta che è necessario recuperare informazioni "combinate".
Dati di esempio
A questo proposito, supponiamo che le tabelle di base siano "popolate" con i dati di esempio mostrati di seguito:
--
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!');
--
Poi, una vista vantaggiosa è quella che riunisce colonne Item
, Car
e 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;
--
Naturalmente, è possibile seguire un approccio simile in modo da poter SELEZIONARE il "pieno" Boat
e le Plane
informazioni direttamente DA una singola tabella (una derivata, in questi casi).
Dopo di che -se non vi importa circa la presenza di segni di NULL nel risultato sets- con la seguente definizione di vista, è possibile, ad esempio, “raccogliere” le colonne dalle tabelle Item
, Car
, Boat
, Plane
e 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;
--
Il codice delle viste qui mostrato è solo illustrativo. Naturalmente, fare alcuni esercizi di prova e modifiche potrebbe aiutare ad accelerare l'esecuzione (fisica) delle query a portata di mano. Inoltre, potrebbe essere necessario rimuovere o aggiungere colonne a tali viste a seconda delle esigenze aziendali.
I dati di esempio e tutte le definizioni della vista sono incorporati in questo SQL Fiddle in modo che possano essere osservati "in azione".
Manipolazione dei dati: codice dei programmi applicativi e alias di colonna
L'uso del codice dei programmi applicativi (se questo è ciò che intendi per "codice specifico lato server") e gli alias di colonna sono altri punti significativi che hai sollevato nei commenti seguenti:
Sono riuscito a risolvere il problema [un JOIN] con il codice specifico lato server, ma in realtà non voglio farlo - E - l'aggiunta di alias a tutte le colonne potrebbe essere "stressante".
Molto ben spiegato, grazie mille. Tuttavia, come sospettavo, dovrò manipolare il set di risultati quando si elencano tutti i dati a causa delle somiglianze con alcune colonne, dal momento che non voglio usare diversi alias per mantenere la dichiarazione più pulita.
È opportuno indicare che mentre si utilizza il codice del programma applicativo è una risorsa molto adatta per gestire le caratteristiche di presentazione (o grafica) dei set di risultati, evitare il recupero dei dati riga per riga è fondamentale per evitare problemi di velocità di esecuzione. L'obiettivo dovrebbe essere "recuperare" i set di dati pertinenti in toto mediante i solidi strumenti di manipolazione dei dati forniti dal motore (precisamente) impostato della piattaforma SQL in modo da poter ottimizzare il comportamento del sistema.
Inoltre, l'utilizzo di alias per rinominare una o più colonne in un determinato ambito può sembrare stressante ma, personalmente, vedo tale risorsa come uno strumento molto potente che aiuta a (i) contestualizzare e (ii) chiarire il significato e l' intenzione attribuiti al colonne; quindi, questo è un aspetto che dovrebbe essere meditato a fondo rispetto alla manipolazione dei dati di interesse.
Scenari simili
Potresti anche trovare di aiuto questa serie di post e questo gruppo di post che contengono la mia opinione su altri due casi che includono associazioni di sottotipi di sottotipo con sottotipi reciprocamente esclusivi.
Ho anche proposto una soluzione per un ambiente aziendale che coinvolge un cluster di sottotipi di sottotipi in cui i sottotipi non si escludono a vicenda in questa (nuova) risposta .
Note finali
1 Integration Definition for Information Modeling ( IDEF1X ) è una tecnica di modellazione dei dati altamente raccomandabile che è stata stabilita come standard nel dicembre 1993 dal National Institute of Standards and Technology (NIST)degli Stati Uniti. È solidamente basato su (a) alcune delle opere teoriche create dall'unico creatore del modello relazionale , ovvero il Dr. EF Codd ; su (b) il punto di vista entità-relazione , sviluppato dal Dr. PP Chen ; e anche su (c) la tecnica di progettazione del database logico, creata da Robert G. Brown.
2 In IDEF1X, un nome ruolo è un'etichetta distintiva assegnata a una proprietà (o attributo) FK per esprimere il significato che detiene nell'ambito del rispettivo tipo di entità.
3 Lo standard IDEF1X definisce la migrazione delle chiavi come "Il processo di modellizzazione del posizionamento della chiave primaria di un'entità padre o generica nella sua entità figlio o categoria come chiave esterna".
Item
tabella include unaCategoryCode
colonna. Come menzionato nella sezione intitolata "Considerazioni sull'integrità e la coerenza":