web-dev-qa-db-fra.com

En SQL, est-il acceptable que deux tables se réfèrent l'une à l'autre?

Dans ce système, nous stockons des produits, des images de produits (il peut y avoir plusieurs images pour un produit) et une image par défaut pour un produit. La base de données:

CREATE TABLE  `products` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `NAME` varchar(255) NOT NULL,
  `DESCRIPTION` text NOT NULL,
  `ENABLED` tinyint(1) NOT NULL DEFAULT '1',
  `DATEADDED` datetime NOT NULL,
  `DEFAULT_PICTURE_ID` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`ID`),
  KEY `Index_2` (`DATEADDED`),
  KEY `FK_products_1` (`DEFAULT_PICTURE_ID`),
  CONSTRAINT `FK_products_1` FOREIGN KEY (`DEFAULT_PICTURE_ID`) REFERENCES `products_pictures` (`ID`) ON DELETE SET NULL ON UPDATE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;


CREATE TABLE  `products_pictures` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `IMG_PATH` varchar(255) NOT NULL,
  `PRODUCT_ID` int(10) unsigned NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `FK_products_pictures_1` (`PRODUCT_ID`),
  CONSTRAINT `FK_products_pictures_1` FOREIGN KEY (`PRODUCT_ID`) REFERENCES `products` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

comme vous pouvez le voir, products_pictures.PRODUCT_ID -> products.ID et products.DEFAULT_PICTURE_ID -> products_pictures.ID, donc une référence de cycle. Est-ce que c'est bon?

30
John Smith

Non, ça ne va pas. Les références circulaires entre les tables sont en désordre. Voir cet article (décennal): SQL By Design: la référence circulaire

Certains SGBD peuvent les gérer, avec une attention particulière, mais MySQL aura des problèmes.


Le premier choix est, selon votre conception, de rendre l’un des deux FK nulles. Cela vous permet de résoudre le problème du poulet et des œufs (dans quel tableau dois-je d'abord insérer?).

Il y a un problème avec votre code. Cela permettra à un produit d’avoir une image par défaut, l’image référencant un autre produit!

Pour interdire une telle erreur, votre contrainte FK devrait être:

CONSTRAINT FK_products_1 
  FOREIGN KEY (id, default_picture_id) 
  REFERENCES products_pictures (product_id, id)
  ON DELETE RESTRICT                            --- the SET NULL options would 
  ON UPDATE RESTRICT                            --- lead to other issues

Cela nécessite une contrainte/index UNIQUE dans la table products_pictures sur (product_id, id) pour que le FK ci-dessus soit défini et fonctionne correctement.


Une autre approche consiste à supprimer la colonne Default_Picture_ID de la table product et à ajouter une colonne IsDefault BIT à la table picture. Le problème avec cette solution est de ne permettre à une seule image par produit d’avoir ce bit et à toutes les autres de l’avoir. Dans SQL-Server (et je pense dans Postgres), cela peut être fait avec un index partiel:

CREATE UNIQUE INDEX is_DefaultPicture 
  ON products_pictures (Product_ID)
  WHERE IsDefault = 1 ;

Mais MySQL n'a pas cette fonctionnalité.


Une troisième approche, qui vous permet même de définir les deux colonnes FK en tant que NOT NULL, consiste à utiliser des contraintes pouvant être différées. Cela fonctionne dans PostgreSQL et je pense dans Oracle. Vérifiez cette question et la réponse de @Erwin: Contrainte de clé étrangère complexe dans SQLAlchemy (le Toutes les colonnes de clé NOT NULL Part).

Les contraintes dans MySQL ne peuvent pas être différées.


Une quatrième approche (que je trouve la plus propre) consiste à supprimer la colonne Default_Picture_ID et à ajouter une autre table. Aucun chemin circulaire dans les contraintes FK et toutes les colonnes FK ne sera NOT NULL avec cette solution:

product_default_picture
----------------------
product_id          NOT NULL
default_picture_id  NOT NULL
PRIMARY KEY (product_id)
FOREIGN KEY (product_id, default_picture_id)
  REFERENCES products_pictures (product_id, id)

Cela nécessitera également un UNIQUE contrainte/index dans la table products_pictures sur (product_id, id) comme dans la solution 1.


Pour résumer, avec MySQL, vous avez deux options:

  • option 1 (une colonne FK nullable) avec la correction ci-dessus pour appliquer correctement l'intégrité

  • option 4 (pas de colonnes FK nullables)

40
ypercubeᵀᴹ

ceci est juste une suggestion mais si possible créer une table de jointure entre cette table pourrait être utile au suivi

product_productcat_join
------------------------
ID(PK)
ProductID(FK)- product table primary key
PictureID(FK) - category table primary key
4
Pranay Rana

Le seul problème que vous allez rencontrer, c'est quand vous faites des insertions. Lequel insérez-vous en premier?

Avec cela, vous devrez faire quelque chose comme:

  • Insérer un produit avec une image par défaut nulle
  • Insérer des images avec le nouvel ID de produit créé
  • Mettez à jour le produit pour définir l'image par défaut comme celle que vous venez d'insérer.

Encore une fois, supprimer ne sera pas amusant.

John ne fait rien de mal à votre travail, mais utiliser PK-FK vous aide en fait à normaliser vos données en supprimant les données redondantes. Qui a des avantages fantastiques de

  • Intégrité des données améliorée grâce à l'élimination des emplacements de stockage en double pour les mêmes données
  • Réduction des conflits de verrouillage et amélioration de la concurrence d'accès entre plusieurs utilisateurs
  • Fichiers plus petits
0
Killrawr