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

Posted

技术标签:

【中文标题】在SQL中,两个表可以互相引用吗?【英文标题】:In SQL, is it OK for two tables to refer to each other? 【发布时间】:2012-05-04 09:58:01 【问题描述】:

在这个系统中,我们存储产品、产品图像(一个产品可以有多个图像)和一个产品的默认图像。数据库:

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,所以是循环引用。可以吗?

【问题讨论】:

为什么不在图像中添加一列以将图像标记为默认值 【参考方案1】:

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

一些 DBMS 可以处理这些问题,而且要特别小心,但 mysql 会出现问题。


选项 1

作为您的设计,使两个 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

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


选项 2

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

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

但是 MySQL 没有这样的功能。


选项 3

这种方法甚至允许您将两个 FK 列定义为 NOT NULL 是使用可延迟约束。这适用于 PostgreSQL,我认为适用于 Oracle。检查这个问题和@Erwin 的答案:Complex foreign key constraint in SQLAlchemy(All key columns NOT NULL 部分)。

MySQL 中的约束不能延迟。


选项 4

方法(我觉得最干净)是删除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)

这还需要(product_id, id) 上的表products_pictures 中的UNIQUE 约束/索引,如解决方案1 ​​中所示。


总而言之,使用 MySQL,您有两种选择:

选项 1(一个可以为空的 FK 列)以及上面的更正以正确执行完整性

选项 4(不可为空的 FK 列)

【讨论】:

这是一个很好的概述 (+1),但我看不出选项 4 比选项 1 有什么优势。使用选项 1,有缺陷的应用程序代码可以通过创建产品来创建无效数据,并且永远不会给它一个default_picture_id。使用选项 4,有缺陷的应用程序代码同样可以通过创建产品而不为它创建 product_default_picture 行来创建无效数据。但是,现在选择产品及其默认图片需要更多的连接,并且架构的自我记录较少。与抵消额外复杂性的初始方法相比,我们获得了什么好处? @MarkAmery 再加入一个不是问题,是吗?当需要为默认图片存储更多属性时,第 4 种解决方案的优势就显现出来了。这也可以在第一个解决方案中使用,使用可为空的列,但 MySQL 在这种情况下无法强制执行 CHECK 约束(以确保所有这些额外属性都是 NULL 或 NOT NULL)。 第一个解决方案也有一些复杂性问题。必须注意如何插入行(首先插入产品,然后插入其图片,然后更新产品以指向默认图片。)当您想要更改默认图片或想要删除产品。 @MarkAmery 如果使用“无效数据”,您的意思是选项(1 和 4)不强制所有产品都有默认图片,您是 100% 正确的,但它们没有。他们不能。我的回答中没有提到这一点。我认为这只能在选项 3 中完成,具有可延迟的约束和不可为空的列。在所有其他选项中,不能仅使用 DDL 完成。 @ypercube 是的,您已经正确理解了我所说的“无效数据”的含义。我意识到在 MySQL 中没有干净的解决方案。我不确定第一个解决方案是否存在第四个解决方案没有的复杂性问题;您已经列出了创建产品所需的 3 个步骤,但是第四个解决方案下的过程几乎相同(唯一的区别是您插入的是 product_default_picture 行而不是更新产品行),我想这是我的主要观点。 +1 对您的第一条评论,尽管想要额外的 NOT NULL 列包含有关关系的信息。【参考方案2】:

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

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

插入默认图片为空的产品 使用新创建的产品 ID 插入图片 更新产品以将默认图片设置为您刚刚插入的图片。

同样,删除不会很有趣。

【讨论】:

【参考方案3】:

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

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

【讨论】:

产品和图片可以有行,product_productcat_join没有。一个有缺陷的应用程序可以做到这一点。你的答案,和@ypercube 的答案一样,根本没用。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

John 您所做的并没有什么坏处,但使用 PK-FK 实际上有助于通过删除冗余重复数据来规范您的数据。

由于消除了相同数据的重复存储位置,提高了数据完整性 减少锁定争用并提高多用户并发性 较小的文件

【讨论】:

【参考方案6】:

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

【讨论】:

我的意思是,“A”指的是“B”,“B”指的是“A”,这不痛吗? 这里没有什么问题,我相信你应该看看 E F Codd 的规则,如果可能的话看看 RDBMS

以上是关于在SQL中,两个表可以互相引用吗?的主要内容,如果未能解决你的问题,请参考以下文章

SQL 在视图中使用交叉引用两个表的结果创建列

SQL,根据第三个表从两个表中选择,它引用它们

如何将两个表与 SQL Server 中第二个表中引用同一列的两列连接起来

SQL角色扮演游戏——引用技能表的类表

c++中两个类互相引用的问题

引用另外表格,提示 #REF!路径也设置好了。被引用的表格不打开就错误,打开就正常。不用vba能处理吗?