5

可能重复:
外键约束可能导致循环或多个级联路径?

我有一个包含四个主表的联系方式数据库:

  • 组织
    • 部门
      • 部分
        • 人们

这是一个图表(箭头表示外键约束):

重要提示:我不小心将绿色箭头标记为指向per_PerID。它应该指向per_SecID

在此处输入图像描述

我已经创建了约束,以确保每个部门的记录都属于一个组织,每个部分都属于一个部门,等等。这些外键约束将其操作设置为CASCADE,因此删除组织将删除所有相应的部门等。

问题是每个组织都需要有一个负责人。这转化为包含负责人 ID 的字段的外键约束。创建约束很容易,但是当我尝试将ON DELETE操作设置为 时SET NULL,出现以下错误:

在表上引入 FOREIGN KEY 约束 ... 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

CASCADE我的推理是,只有当我将操作设置为, not时才会发生循环SET NULL,但显然这是不正确的。

  1. 个人记录已删除
  2. org_incharge_PerID在相应的组织记录中设置为空
  3. 不会发生进一步的传播。如果组织被删除了,就会有问题,因为部门也指组织。就目前的情况来看,应该没有问题。

是否有另一种方法来确保引用完整性,或者我应该重新考虑我的表结构,以完全避免循环问题?

我想要的是org_incharge_PerID被限制per_PerID在 people 表中的 s 集合中。NULL此外,如果该人被删除,我希望将值设置为。

4

1 回答 1

5

一旦遇到循环警告,在不更改数据库结构的情况下确保引用完整性的唯一另一种方法是对其中一个约束使用触发器。触发警告的问题是双重的:

  1. 该表与该Sections表共享两个相反方向的外键约束Persons
  2. 所有四个表都有一个完整的循环,所有这些表都以相同的方向进行,Person->Section->Department->Organization。

此外,表格设计存在一个问题:它的构造方式,一个人可能负责他不属于的部分。

InCharge您可以通过从 中删除列Sections并引入另一个表来解决这三个问题SectionInCharge, 包含列PerIDSecID,在 上具有聚集索引/唯一约束,并且在两列上都具有表SecID的复合外键。Persons您需要向表中的两列添加 UNIQUE 约束,Persons以便 FK 可以与它​​们相关联。

现在您可以将新的 FK 设置为 ON DELETE CASCADE 并删除。

使用行的存在或不存在来表示某事而不是使用可为空的列通常是一种优越的数据库设计模式。

现在我想一想,其他带有“负责人”列的表在允许分配不正确的成员的人方面也存在类似的问题。我知道使用 FK 解决这个问题的唯一方法(在我看来这是触发器的最佳实践)是通过表传播列并将它们包含在复合键中。这具有使事情变得有点尴尬的不幸副作用,但它还不错,因为您可以将最具体的列放在第一位,并仅在可能的情况下加入它。因此,您可以将 OrgID 放入Section表中,并在 Dept 表上使用复合 FK (DeptID, OrgID),然后将DeptIDandOrgID也放入Persons表中。然后,您将需要DeptInChargeOrgInCharge表。

您可能感兴趣的关于这一点(这并不是真正解决您提出的核心问题)的另一个注意事项是将您的三个底部表格组合成一个表格的可能性,OrgUnit. 添加一个OrgUnitType列和一个 ParentOrgID 列。当事物不具有该属性时添加将为 NULL 的列(如在没有传真号码的部分中),或者使现有的组织、部门和部分表成为 OrgUnit的子类型(数据库超类型/子类型的示例)。这样做的好处是,你可以在任何地方引用这三种类型中的任何一种:如果 FK 是 OrgUnit,它可以是其中任何一种。但是,如果它只允许作为一个部分,那么您将改为对那个表进行 FK。事实上,这对于单身人士来说是完美的OrgUnitInCharge表,带有 FK 到OrgUnit (OrgUnitID, OrgUnitTypeID).

我知道这里有一些松散的结尾,我还没有将它们完全捆绑成一个漂亮的包给你,但我希望到目前为止我所描述的内容是有帮助的。

PS我希望你能原谅我增加了风格上的考虑。对我来说,在每个表的每一列上放置一个表表示前缀是很多额外的不必要的装饰。根据我的感觉,我输入 90-110 wpm,但我仍然讨厌打字。此外,我坚信在我的代码中保持高信噪比,而对我来说,到处爆炸的前缀大大降低了这一点。在 SQL 查询中,最佳实践是使用表别名,然后在每列上使用这些别名,如SELECT P.Name FROM dbo.Person P. 为了解决多个表具有相同列名的问题,我已经开始将表中的典型WidgetNameWidget也设置为Widget. 作为为什么高信噪比如此重要的一个例子: vrbaux 可以让您阅读艺术这句话 advQuickly conAnd advEasily?确实,附加前缀包含有用的信息,但这些有用的信息只有一点用处,而且分散注意力。如果您现在从头开始设计数据库,我想请求您不要使用这些前缀,因为某个奇怪的巧合有一天最终会维护它(发生了奇怪的事情)。:)

PPS 我喜欢 PascalCase 而不是 under_scores,因为它更容易输入。下划线位于最上面一行并被最弱的手指击中 - 所以它更慢且容易出错。

PPPS 我也更喜欢单数表名,但这既不是这里也不是那里?我提前为你对风格的批评感到抱歉。

于 2012-12-09T08:14:22.857 回答