在SQL中,两个表是否可以相互引用?

时间:2012-05-04 09:58:01

标签: mysql database database-design

在此系统中,我们存储产品,产品图像(产品可能有许多图像),以及产品的默认图像。数据库:

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;

正如您所见,products_pictures.PRODUCT_ID -> products.IDproducts.DEFAULT_PICTURE_ID -> products_pictures.ID,所以是循环参考。可以吗?

6 个答案:

答案 0 :(得分:42)

不,不行。表之间的循环引用很混乱。见这篇(十年前的)文章:SQL By Design: The Circular Reference

有些DBMS可以处理这些问题,并且需要特别小心,但MySQL会遇到问题。


作为您的设计,第一个选择是使两个FK中的一个可以为空。这允许你解决鸡和蛋的问题(我应该首先插入哪个表?)。

您的代码存在问题。它将允许产品具有默认图片,其中该图片将引用另一个产品!

要禁止此类错误,您的FK约束应为:

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

这需要在UNIQUE上的products_pictures表中(product_id, id)约束/索引,以便定义上述FK并正常工作。


另一种方法是从Default_Picture_ID表中删除product列,并在IsDefault BIT表中添加picture列。这个解决方案的问题是如何只允许每个产品一张图片打开,其他所有产品都关闭。在SQL-Server中(我认为在Postgres中)可以使用部分索引来完成:

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

但MySQL没有这样的功能。


第三种方法,即使将两个FK列定义为NOT NULL,也可以使用可延迟约束。这适用于PostgreSQL,我想在Oracle中。请查看@Erwin的问题和答案:Complex foreign key constraint in SQLAlchemy所有关键列NOT NULL 部分)。

MySQL中的约束无法推迟。


第四种方法(我认为最干净)是删除Default_Picture_ID列并添加另一个表。使用此解决方案时,FK约束中的圆形路径和所有FK列都不是NOT NULL

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)

这也需要在UNIQUE上的products_pictures表中(product_id, id)约束/索引,与解决方案1中一样。


总结一下,使用MySQL有两种选择:

  • 选项1(可以为空的FK列)并进行上述更正以正确执行完整性

  • 选项4(无可空的FK列)

答案 1 :(得分:4)

这只是建议,但如果可能的话,在此表之间创建一个联接表可能有助于跟踪

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

答案 2 :(得分:2)

您将遇到的唯一问题是何时进行插入。 你先插入哪一个?

有了这个,你将不得不做类似的事情:

  • 插入带有默认图片的产品
  • 使用新创建的产品ID插入图片
  • 更新产品,将默认图片设置为刚插入的图片。

同样,删除也不会很有趣。

答案 3 :(得分:1)

在另一个表中,您可以仅保留该字段而没有外键约束。 在某些情况下,如果您要使用较小的表进行处理,但要根据处理结果连接到较大的表,则很有用。

例如,如果您添加一个product_location表,其中包含国家,地区,城市,地址以及经度和纬度信息。在某些情况下,您可能希望在地图上的圆圈内显示产品。

答案 4 :(得分:0)

John你做的不是什么坏事,但使用PK-FK实际上有助于通过删除多余的重复数据来规范化数据。

具有一些奇妙的优势
  • 由于消除了相同数据的重复存储位置,提高了数据完整性
  • 减少锁定争用并改善多用户并发性
  • 较小的文件

答案 5 :(得分:-2)

这不是循环引用,即pk-fk