1

我面临这个问题:

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

这些是约束:

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

如何在 SQL 中设计这个?

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

Parent table:
parentId
favouriteChildId NOT NULL
leastFavouriteChildId NULL

Child table:
childId
parentId

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


编辑:只是为了增加一些清晰度,这里是问题上下文的一部分:

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

PriceGroup 有多个价格,一个强制 mainPrice (favouriteChild) 并且可以有一个 officialPrice (leastFavouriteChild)。

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

4

3 回答 3

3

在你给出的业务规则之外

  1. 每位父母必须至少有一个孩子,
  2. 每个父母都必须有一个最喜欢的孩子,
  3. 每个父母都可以有一个最不喜欢的孩子

您的解决方案

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 不为 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 与 INCOMPLETE)并将 favouriteChildId 设置为 NULLable FK,但是当将状态更新为 COMPLETE 时,您可以使用触发器检查是否尊重完整性。

这需要额外的列,但可以使事情变得非常干净(您实际上可以在此表上创建一个视图,该视图仅公开已完成的记录,有效地使其看起来像具有 FK NOT NULL 的表)。

于 2011-05-16T10:48:54.697 回答
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 Favorite Child 中插入一行;仅当存在最不喜欢的孩子时,才会将其插入 Is Least Favorite 中:由视图上的触发器完成的插入、更新、删除;通过表上的左连接进行选择。

这不涉及 Favourite Child 关系的强制性质 - 这必须由插入/更新/删除触发器处理。

于 2011-05-16T10:28:05.770 回答
0

如果只有一层深:

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

EveryChild必须总是恰好有 1Parent并且Parents总是有>= 0 Children。(没有办法解决这个问题。)

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

Items ( ItemID, ParentItemID NULL, Title, etc )

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

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

于 2011-05-16T10:08:00.643 回答