Ce que vous décrivez s'appelle les associations polymorphes. Autrement dit, la colonne "clé étrangère" contient une valeur d'ID qui doit exister dans l'une des tables cible. En règle générale, les tables cibles sont liées d'une manière ou d'une autre, par exemple en tant qu'instances d'une superclasse de données commune. Vous auriez également besoin d'une autre colonne à côté de la colonne de clé étrangère, de sorte que sur chaque ligne, vous puissiez désigner la table cible référencée.
CREATE TABLE popular_places (
user_id INT NOT NULL,
place_id INT NOT NULL,
place_type VARCHAR(10)
);
Il n'y a aucun moyen de modéliser les associations polymorphes à l'aide de contraintes SQL. Une contrainte de clé étrangère fait toujours référence à une table cible.
Les associations polymorphes sont prises en charge par des frameworks tels que Rails et Hibernate. Mais ils disent explicitement que vous devez désactiver les contraintes SQL pour utiliser cette fonctionnalité. Au lieu de cela, l'application ou le cadre doit faire un travail équivalent pour s'assurer que la référence est satisfaite. Autrement dit, la valeur de la clé étrangère est présente dans l'une des tables cibles possibles.
Les associations polymorphes sont faibles en ce qui concerne l'application de la cohérence de la base de données. L'intégrité des données dépend de tous les clients accédant à la base de données avec la même logique d'intégrité référentielle appliquée, et l'application doit également être sans bogue.
Voici quelques solutions alternatives qui tirent parti de l'intégrité référentielle appliquée par la base de données:
Créez une table supplémentaire par cible. Par exemple popular_states
et popular_countries
, quelle référence states
et countries
respectivement. Chacun de ces tableaux «populaires» fait également référence au profil de l'utilisateur.
CREATE TABLE popular_states (
state_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(state_id, user_id),
FOREIGN KEY (state_id) REFERENCES states(state_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
CREATE TABLE popular_countries (
country_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(country_id, user_id),
FOREIGN KEY (country_id) REFERENCES countries(country_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
Cela signifie que pour obtenir tous les emplacements préférés d'un utilisateur, vous devez interroger ces deux tables. Mais cela signifie que vous pouvez compter sur la base de données pour garantir la cohérence.
Créez une places
table comme supertable. Comme le mentionne Abie, une deuxième alternative est que vos lieux populaires référencent une table comme places
, qui est un parent à la fois states
et countries
. Autrement dit, les États et les pays ont également une clé étrangère vers places
(vous pouvez même faire de cette clé étrangère la clé primaire de states
et countries
).
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
PRIMARY KEY (user_id, place_id),
FOREIGN KEY (place_id) REFERENCES places(place_id)
);
CREATE TABLE states (
state_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (state_id) REFERENCES places(place_id)
);
CREATE TABLE countries (
country_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Utilisez deux colonnes. Au lieu d'une colonne pouvant faire référence à l'une des deux tables cibles, utilisez deux colonnes. Ces deux colonnes peuvent être NULL
; en fait, un seul d'entre eux devrait être non- NULL
.
CREATE TABLE popular_areas (
place_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
state_id INT,
country_id INT,
CONSTRAINT UNIQUE (user_id, state_id, country_id),
CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
FOREIGN KEY (state_id) REFERENCES places(place_id),
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
En termes de théorie relationnelle, les associations polymorphes viole la première forme normale , car il popular_place_id
s'agit en fait d'une colonne avec deux significations: c'est soit un état, soit un pays. Vous ne stockeriez pas une personne age
et sa phone_number
dans une seule colonne, et pour la même raison, vous ne devriez pas stocker les deux state_id
et country_id
dans une seule colonne. Le fait que ces deux attributs aient des types de données compatibles est une coïncidence; ils signifient encore différentes entités logiques.
Les associations polymorphes violent également la troisième forme normale , car la signification de la colonne dépend de la colonne supplémentaire qui nomme la table à laquelle la clé étrangère fait référence. Dans la troisième forme normale, un attribut dans une table doit dépendre uniquement de la clé primaire de cette table.
Re commentaire de @SavasVedova:
Je ne suis pas sûr de suivre votre description sans voir les définitions de table ou un exemple de requête, mais il semble que vous ayez simplement plusieurs Filters
tables, chacune contenant une clé étrangère qui fait référence à une Products
table centrale .
CREATE TABLE Products (
product_id INT PRIMARY KEY
);
CREATE TABLE FiltersType1 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
CREATE TABLE FiltersType2 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
...and other filter tables...
Il est facile d'associer les produits à un type de filtre spécifique si vous savez à quel type vous souhaitez vous joindre:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Si vous souhaitez que le type de filtre soit dynamique, vous devez écrire le code d'application pour construire la requête SQL. SQL exige que la table soit spécifiée et corrigée au moment où vous écrivez la requête. Vous ne pouvez pas choisir la table jointe de manière dynamique en fonction des valeurs trouvées dans des lignes individuelles de Products
.
La seule autre option consiste à joindre toutes les tables de filtrage à l'aide de jointures externes. Ceux qui n'ont pas de product_id correspondant seront simplement renvoyés sous la forme d'une seule ligne de valeurs nulles. Mais vous devez toujours coder en dur toutes les tables jointes, et si vous ajoutez de nouvelles tables de filtrage, vous devez mettre à jour votre code.
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Une autre façon de joindre toutes les tables de filtrage consiste à le faire en série:
SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
Mais ce format vous oblige toujours à écrire des références à toutes les tables. Il n'y a pas moyen de contourner cela.
join
cible changera également ...... Est-ce que je complique trop ou quoi? Aidez-moi!