一对多,父子表在MySQL中的循环关系

时间:2011-05-16 10:00:54

标签: mysql sql parent-child circular-reference

我正面临这个问题:

我有一个父表和一个子表,一个父表可以有多个子表,标准故事。

这些是限制因素:

  • 每个父母必须至少有一个孩子,
  • 每个家长必须有一个最喜欢的孩子,
  • 每个家长可以拥有一个最不喜欢的孩子

如何在SQL中设计这个?

由于循环关系,我不确定是否可以使用标准的父子表:

Parent table:
parentId
favouriteChildId NOT NULL
leastFavouriteChildId NULL

Child table:
childId
parentId

我在考虑使用桥接表,但我不确定如何对这些约束进行建模。


编辑:只是为了增加一些清晰度,这是问题背景的一部分:

有Price表(子)和PriceGroup表(父)。

PriceGroup有多个价格,一个是强制性的mainPrice(favouriteChild),可以有一个官方价格(lessFavouriteChild)。

以下与问题无关,但对上下文有所了解: 价格根据他们所参考的产品进行分组,一个产品可以有多个价格 - 然后将这些价格分组到价格组中,每个组需要参考主要价格和官方价格(如果有的话)。

3 个答案:

答案 0 :(得分:3)

根据您提供的业务规则

  1. 每位家长必须至少有一个孩子,
  2. 每个家长必须有一个最喜欢的孩子,
  3. 每位家长可以有一个最不喜欢的孩子
  4. 您的解决方案

    Parent table:
    parentId (PK)
    favouriteChildId NOT NULL (FK)
    leastFavouriteChildId NULL (FK)
    
    Child table:
    childId (PK)
    parentId (FK)
    

    满足2和3。 但它也满足1(因为favouriteChildId NOT NULL不允许创建没有子节点的父记录)。

    由于您已经拥有上述内容,我将假设您真正的问题是如何在Child表中使parentId为NOT NULL。

    通常,在SQL中有一些规定,以便您可以执行类似

    的操作
    BEGIN TRANS
    INSERT INTO TABLE1 (FK not checked yet)
    INSERT INTO TABLE2 (FK not checked yet)
    COMMIT (All integrity checked)
    

    在这种情况下,“循环引用”不会成为问题(参见DEFERRED

    Mysql不支持它,因此您有以下选项

    <强>触发器:
    可以假设在插入父记录时已经知道了一个喜欢的孩子,那么你可以在父表上插入之前运行一个触发器并且

    • 将喜欢的孩子插入子表中获取其ID
    • 插入包含孩子ID
    • 的父记录

    注意:问题在于,您可以通过这种方式正式满足条件,但是要先插入子记录,您必须在父级中使用其他列,以便触发器可以了解子表中的其他字段或插入空白记录(在任何一种情况下设计都不干净)

    通过安全保持诚信
    以上可以作为存储过程实现,而不需要父表级别的其他字段。但是,通常可以绕过存储过程,因此它不符合真正的完整性规则。

    通过存储过程实现某些功能的通用方法符合完整性规则 - 即删除这些表的所有常规用户(和应用程序)的写权限,并允许仅通过存储来更改数据过程

    编辑: 关于触发器,还有一种方法可以使用触发器实现规则,即接受您必须单独插入记录,并且您必须在某一时刻拥有违反业务规则的数据。

    在这种情况下,您可以为父记录创建一个STATUS属性(例如:COMPLETE vs INCOMPLETE)并使favouriteChildId为NULLable FK,但是当将状态更新为COMPLETE时,您可以触发检查是否遵守完整性。

    这需要额外的列,但可以使事情变得非常干净(你实际上可以在这个表上创建一个只暴露完整记录的视图,有效地使它看起来就像使用FK NOT的表一样NULL)。

答案 1 :(得分:2)

您可以建模(在某种程度上)其他约束:

Parent Table
parentId (PK)

Child table:
childId  (PK)
parentId (FK)

Is Favorite table:
childID (PK)(FK)

Is Least Favourite table:
ChildID (PK)(FK)

总是会将一行插入到“收藏的孩子”中;只有在最不喜欢的孩子的情况下,才会在Is Least Favorite中插入一个:在视图上通过触发器插入,更新,删除;左边选择加入表格。

这不涉及Favorite Child关系的强制性质 - 必须由insert / update / delete触发器处理。

答案 2 :(得分:0)

如果只有一个级别:

Parents ( ParentID, Title, etc )
Children ( ChildID, ParentID, Title, etc )

每个Child必须始终只有1 ParentParents始终有>= 0 Children。 (没有办法解决这个问题。)

如果多个(未知)级别深度:

Items ( ItemID, ParentItemID NULL, Title, etc )

非常简单:Items.ParentItemID = Items.ItemID

修改
如果您需要多个(未知)级别,查询将是多个,并且缓存总结果将是一个非常非常好的主意。 (每个孩子都会有另一个查询来获取它的直接孩子等等。)