204 votes

Possible de faire une clé étrangère de MySQL vers une des deux tables possibles ?

Eh bien, voici mon problème j'ai trois tables; les régions, les pays, les états. Les pays peuvent être à l'intérieur des régions, les états peuvent être à l'intérieur des régions. Les régions sont au sommet de la chaîne alimentaire.

Maintenant, je vais ajouter une popular_areas tableau avec deux colonnes; region_id et popular_place_id. Est-il possible de faire popular_place_id être un FK soit de pays OU états. Je vais probablement ajouter un popular_place_type colonne afin de déterminer si l'id est de décrire un pays ou un état à l'autre.

323voto

Bill Karwin Points 204877

Ce que vous décrivez s'appelle Polymorphes Associations. C'est, la "clé étrangère" colonne contient une valeur d'id qui doit exister dans une série de tableaux. Généralement la cible tables sont liées d'une certaine façon, comme étant des instances de certaines communes de la superclasse de données. Vous auriez également besoin d'une autre colonne sur le côté de la colonne de clé étrangère, de sorte que sur chaque ligne, vous pouvez désigner la table cible est référencé.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

Il n'y a aucun moyen de modèle Polymorphes Associations à l'aide de SQL contraintes. Une contrainte de clé étrangère fait toujours référence à une table cible.

Polymorphes Associations sont pris en charge par des cadres tels que des Rails et de la mise en veille prolongée. Mais ils explicitement dire que vous devez désactiver SQL contraintes 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 satisfait. C'est la valeur de la clé étrangère est présente dans une des tables cible.

Polymorphes Associations sont faibles à l'égard de l'application de cohérence de base de données. L'intégrité des données dépend de tous les clients qui accèdent à la base de données avec la même intégrité référentielle de la logique appliquée, et aussi l'application de la loi doit être exempt de bogues.

Voici quelques solutions de rechange pour faire profiter de la base de données de l'exécution de l'intégrité référentielle:

Créer une table supplémentaire par cible. Par exemple popular_states et popular_countries, qui référence states et countries respectivement. Chacun de ces "populaire" tables de référence également le 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 toutes d'un utilisateur à un favori populaire endroits où vous devez vous interroger à la fois de ces tables. Mais cela signifie que vous pouvez compter sur la base de données pour appliquer la cohérence.

Créer un places tableau comme un supertable. Comme Abie mentionne, une deuxième alternative est que vos endroits populaires référence à un tableau comme places, ce qui est un parent à la fois states et countries. C'est à la fois des états et des pays ont aussi une clé étrangère d' places (vous pouvez même faire cette clé étrangère aussi être la clé primaire de l' 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)
);

L'utilisation de deux colonnes. Au lieu d'une colonne qui pourrait faire référence à l'une des deux tables cible, l'utilisation de deux colonnes. Ces deux colonnes peuvent être NULL; en fait, un seul d'entre eux doit ê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), -- UNIQUE permits NULLs
  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 la théorie relationnelle, Polymorphe Associations viole Première Forme Normale, car l' popular_place_id est en effet une colonne avec deux significations: c'est soit un état ou un pays. Vous n'auriez pas de magasin d'une personne, age et leur phone_number dans une seule colonne, et pour la même raison, vous ne devez pas stocker à la fois state_id et country_id dans une seule colonne. Le fait que ces deux attributs ont des types de données compatibles est fortuite; ils encore signifier différentes entités logiques.

Polymorphes Associations viole aussi la Troisième Forme Normale, parce que le sens de la colonne dépend de la colonne supplémentaire qui les noms de la table à laquelle la clé étrangère référence. En 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 simplement avoir plusieurs Filters tables, chacune contenant une clé étrangère qui fait référence à une centrale d' Products table.

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...

Rejoignant les produits à un type spécifique de filtre est facile si vous savez de quel type que vous souhaitez rejoindre:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

Si vous souhaitez que le type de filtre dynamique, vous devez écrire le code de l'application pour construire la requête SQL. SQL requiert de la table spécifiée et fixé au moment de vous écrire la requête. Vous ne pouvez pas faire la table jointe être choisis de manière dynamique sur la base des valeurs trouvées dans les différents lignes de Products.

La seule autre option est d'adhérer à toutes les tables de filtrage à l'aide de jointures externes. Ceux qui n'ont pas de correspondance product_id va juste être retournés comme une ligne unique de valeurs null. Mais vous avez encore à 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 est de 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 nécessite encore à vous d'écrire les références à toutes les tables. Il n'y a pas moyen de contourner cela.

13voto

Abie Points 7175

Ce n'est pas la solution la plus élégante dans le monde, mais vous pouvez utiliser le béton de l'héritage de table pour faire ce travail.

Conceptuellement vous proposons une notion de classe de "choses qui peuvent être populaire zones" à partir de laquelle vos trois types de lieux hériter. Vous pourriez présenter cela comme une table appelée, par exemple, places où chaque ligne est un one-to-one relation avec une ligne en regions, countriesou states. (Les attributs qui sont partagés entre les régions, les pays ou états, le cas échéant, pourrait être poussé dans cette endroits de la table.) Votre popular_place_id serait alors une clé étrangère référence à une ligne dans les endroits de la table, qui pourrait mener à une région, d'un pays ou de l'é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 les Rails poignées polymorphes associations, mais je ne suis pas un fan de cette en général. Le projet de loi explique en détail excellente pourquoi polymorphes les associations ne sont pas vos amis.

2voto

jdelator Points 1336
Non, cela n'est pas possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X