如何与有特权的孩子建立一对多关系?


22

我想建立一对多关系,其中对于每个父母,一个或零个孩子被标记为“收藏夹”。但是,并不是每个父母都会有一个孩子。(例如,将父母视为本网站上的问题,将孩子视为答案,将喜欢的事物作为接受的答案。)例如,

TableA
    Id            INT PRIMARY KEY

TableB
    Id            INT PRIMARY KEY
    Parent        INT NOT NULL FOREIGN KEY REFERENCES TableA.Id

我看到的方式可以将以下列添加到TableA中:

    FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id

或TableB的以下列:

    IsFavorite    BIT NOT NULL

第一种方法的问题在于它引入了可为空的外键,据我所知,它不是标准化形式。第二种方法的问题是,需要做更多的工作以确保最多一个孩子是最爱的。

我应该使用哪种标准来确定使用哪种方法?或者,还有其他我没有考虑的方法吗?

我正在使用SQL Server 2012。

Answers:


19

另一种方法(没有Null,并且在FOREIGN KEY关系中没有循环)是具有第三个表来存储“喜欢的孩子”。在大多数DBMS中,您需要在上附加UNIQUE约束TableB

@Aaron可以更快地确定上述命名约定非常繁琐,并且可能导致错误。如果您Id的表中没有所有列,并且出现的许多表中的列(连接在一起)具有相同的名称,通常会更好(并使您保持理智)。因此,这是一个重命名:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    UNIQUE (ParentID, ChildID)

FavoriteChild
    ParentID        INT NOT NULL PRIMARY KEY
    ChildID         INT NOT NULL 
    FOREIGN KEY (ParentID, ChildID) 
        REFERENCES Child (ParentID, ChildID)

在(正在使用的)SQL Server中,您还可以选择所IsFavorite提到的位列。每个父母的唯一喜欢的孩子可以通过过滤的唯一索引来实现:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    IsFavorite      BIT NOT NULL

CREATE UNIQUE INDEX is_FavoriteChild
  ON Child (ParentID)
  WHERE IsFavorite = 1 ;

不建议使用选项1的主要原因(至少在SQL-Server中不建议这样做)是,外键引用中的循环路径模式存在一些问题。

阅读一个很老的文章:SQL By Design:循环参考

在两个表中插入或删除行时,您将遇到“鸡与蛋”问题。我应该首先插入哪个表-而不违反任何约束?

为了解决这个问题,您必须定义至少一列可为空。(好的,从技术上讲,您不是必须的,您可以拥有所有列,NOT NULL但只有在DBMS中(例如Postgres和Oracle),它们才实现了可延迟的约束。有关类似问题,请参见@Erwin的回答:有关如何在SQLAlchemy中使用复杂的外键约束这可以在Postgres中完成)。不过,这种设置感觉就像在薄冰上滑冰。

在SO处也检查几乎相同的问题(但对于MySQL),在SQL中,两个表相互引用是否可以?我的答案几乎相同。MySQL没有局部索引,因此唯一可行的选择是可为空的FK和额外的表解决方案。


9

这取决于您的优先级。您是要避免工作还是要遵守最严格的规范化规则?

就我个人而言,我认为最好将其IsFavorite放在子表中,并且愿意进行工作以确保每个父母最多有一个孩子是父母的最爱。主要原因与可空的外键无关:我不喜欢指向两个方向的所有外键。

@ypercube的建议也是一个很好的折衷方案。

顺便说一句,请,请不要用无意义的列名(如)来填充您的架构Id。我更希望Id在整个架构中以有意义的方式命名。它可以识别作者吗?好吧,叫它AuthorID。它代表产品吗?好吧,ProductID。是员工吗?在某些情况下是推荐经理?好了,EmployeeID并且ManagerID更有意义,我比IDParent。尽管将其排除在外似乎是合乎逻辑的(并且将其放入其中是多余的),但是当您开始编写复杂的联接(或在此处发布查询)时,当您尝试反向工程一堆联接时,您肯定会感到有些诅咒。到a.Parent = b.ID...故障等列。


1

数据属于子表。我们使用表上的触发器保持其正确性,以确保将一条记录和唯一记录标记为收藏(或在我们的情况下为首选地址)。

但是,@ ypercube的单独表的想法也是一个不错的主意。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.