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!Ce n'est pas la solution la plus élégante au monde, mais vous pouvez utiliser l' héritage de table concret pour que cela fonctionne.
Conceptuellement, vous proposez une notion d'une classe de «choses qui peuvent être des zones populaires» dont vos trois types d'endroits héritent. Vous pouvez représenter cela comme une table appelée, par exemple,
places
où chaque ligne a une à une relation avec une ligneregions
,countries
oustates
. (Les attributs qui sont partagés entre les régions, les pays ou les états, le cas échéant, pourraient être poussés dans cette table d'endroits.) Votrepopular_place_id
serait alors une référence de clé étrangère à une ligne dans la table d'endroits qui vous mènerait alors à une région, un pays , ou état.La solution que vous proposez avec une deuxième colonne pour décrire le type d'association se trouve être la façon dont Rails gère les associations polymorphes, mais je ne suis pas fan de cela en général. Bill explique en détail pourquoi les associations polymorphes ne sont pas vos amis.
la source
Voici une correction à l'approche «supertable» de Bill Karwin, utilisant une clé composée
( place_type, place_id )
pour résoudre les violations de forme normales perçues:CREATE TABLE places ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) NOT NULL CHECK ( place_type = 'state', 'country' ), UNIQUE ( place_type, place_id ) ); CREATE TABLE states ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) DEFAULT 'state' NOT NULL CHECK ( place_type = 'state' ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) -- attributes specific to states go here ); CREATE TABLE countries ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) DEFAULT 'country' NOT NULL CHECK ( place_type = 'country' ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) -- attributes specific to country go here ); CREATE TABLE popular_areas ( user_id INT NOT NULL, place_id INT NOT NULL, UNIQUE ( user_id, place_id ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) );
Ce que cette conception ne peut pas garantir que pour chaque ligne,
places
il existe une ligne dansstates
oucountries
(mais pas les deux). Il s'agit d'une limitation des clés étrangères en SQL. Dans un SGBD entièrement conforme aux normes SQL-92, vous pouvez définir des contraintes inter-tables reportables qui vous permettraient d'atteindre la même chose, mais c'est maladroit, implique des transactions et un tel SGBD n'a pas encore été commercialisé.la source
Je me rends compte que ce fil est vieux, mais j'ai vu cela et une solution m'est venue à l'esprit et j'ai pensé que je la lancerais là-bas.
Les régions, les pays et les États sont des lieux géographiques qui vivent dans une hiérarchie.
Vous pourriez éviter complètement votre problème en créant une table de domaine appelée Geographic_location_type que vous rempliriez avec trois lignes (Region, Country, State).
Ensuite, au lieu des trois tables de localisation, créez une seule table de localisation géographique qui a une clé étrangère géographique_location_type_id (afin que vous sachiez si l'instance est une région, un pays ou un État).
Modélisez la hiérarchie en rendant cette table auto-référencée afin qu'une instance State contienne la fKey à son instance de Country parente, qui à son tour détient la fKey à son instance de Region parente. Les instances de région contiendraient NULL dans cette fKey. Ce n'est pas différent de ce que vous auriez fait avec les trois tableaux (vous auriez 1 - beaucoup de relations entre la région et le pays et entre le pays et l'état) sauf que maintenant, tout est dans un seul tableau.
La table popular_user_location serait une table de résolution de portée entre l'utilisateur et georgraphical_location (donc de nombreux utilisateurs pourraient aimer de nombreux endroits).
Soooo…
CREATE TABLE [geographical_location_type] ( [geographical_location_type_id] INTEGER NOT NULL, [name] VARCHAR(25) NOT NULL, CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id]) ) -- Add 'Region', 'Country' and 'State' instances to the above table CREATE TABLE [geographical_location] ( [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL, [name] VARCHAR(1024) NOT NULL, [geographical_location_type_id] INTEGER NOT NULL, [geographical_location_parent] BIGINT, -- self referencing; can be null for top-level instances CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id]) ) CREATE TABLE [user] ( [user_id] BIGINT NOT NULL, [login_id] VARCHAR(30) NOT NULL, [password] VARCHAR(512) NOT NULL, CONSTRAINT [PK_user] PRIMARY KEY ([user_id]) ) CREATE TABLE [popular_user_location] ( [popular_user_location_id] BIGINT NOT NULL, [user_id] BIGINT NOT NULL, [geographical_location_id] BIGINT NOT NULL, CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id]) ) ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id]) ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id]) ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id]) ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])
Je ne savais pas quelle était la base de données cible; ce qui précède est MS SQL Server.
la source
Eh bien, j'ai deux tableaux:
a) Numéro du morceau b) Titre du morceau ...
et j'en ai un troisième
Le problème est que certains types de listes de lecture ont des liens vers d'autres listes de lecture. Mais dans mysql, nous n'avons pas de clé étrangère associée à deux tables.
Ma solution: je vais mettre une troisième colonne dans Songs_to_playlist_relation. Cette colonne sera booléenne. Si 1 alors chanson, sinon sera lié à la table de liste de lecture.
Donc:
a) Playlist_number (int) b) Is song (boolean) c) Numéro relatif (numéro de morceau ou numéro de playlist) (int) ( pas de clé étrangère à une table)
#create table chansons queries.append (" SET SQL_MODE =
C'est tout!NO_AUTO_VALUE_ON_ZERO
;") queries.append (" CREATE TABLEsongs
(NUMBER
int ( 11 ) NOT NULL , int ( 11 ) NOT NULL , tinyint ( 1 ) NOT NULL DEFAULT '1' , varchar ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , varchar ( 1000 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOTSONG POSITION
PLAY SONG
SONG TITLE
DESCRIPTION
NULL ,ARTIST
varchar ( 255 ) CARACTÈRE SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος καλλιτέχνης' ,AUTHOR
varchar ( 255 ) CARACTÈRE SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστος στιχουργός' ,COMPOSER
varchar ( 255 ) CARACTÈRE SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT « Άγνωστος συνθέτης ' ,ALBUM
varchar ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Άγνωστο άλμπουμ' ,YEAR
int ( 11 ) NOT NULL DEFAULT '33' ,RATING
int ( 11 ) NOT NULL DEFAULT '5' ,IMAGE
varchar ( 600 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , varchar ( 500 )SONG PATH
CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , int ( 11 ) NOT NULL DEFAULT '0' , float NOT NULL DEFAULT '1' , float NOT NULL DEFAULT '1' ) ENGINE = InnoDB DEFAULT CHARSET = utf8; ")SONG REPEAT
VOLUME
SPEED
queries.append (" ALTER TABLEsongs
ADD PRIMARY KEY (NUMBER
), ADD UNIQUE KEYPOSITION
( ), ADD UNIQUE KEY ( ), ADD UNIQUE KEY ( );")SONG POSITION
TITLE
SONG TITLE
PATH
SONG PATH
queries.append (" ALTER TABLEsongs
MODIFYNUMBER
int ( 11 ) NOT NULL AUTO_INCREMENT;")la source