【问题标题】:Foreign Key Referencing Multiple Tables外键引用多个表
【发布时间】:2014-10-15 16:56:24
【问题描述】:

我有一个带有唯一标识符的列,它可以潜在地引用四个不同表之一。我已经通过两种方式看到了这一点,但似乎都是不好的做法。

首先,我看到了一个没有明确声明为特定表的外键的 ObjectID 列。然后你可以把你想要的任何唯一标识符塞进去。这意味着您可能会从不属于我想要的 4 个表的表中插入 ID。

其次,因为数据可以来自四个不同的表,我还看到人们制作了 4 个不同的外键。在这样做的过程中,系统依赖于 ONE AND ONLY ONE 具有非 NULL 值的列。

有什么更好的方法来做到这一点?例如,我的表中的记录可能会引用 Hospitals(ID)、Clinics(ID)、Schools(ID) 或 Universities(ID)……但仅限于这些表。

谢谢!

【问题讨论】:

  • 拥有一个引用 Buildings(ID) 的键。
  • 那么,建筑物表中有什么?一个 ObjectID 还是四个单独的列?我回到同样的问题了吗?其他四个表已经存在。
  • 要么只是一个 ID;一个 ID 和一个 TypeID;或最好是 ID、TypeID 和医院、诊所、学校和大学共有的所有列。然后每个表的 ID 实际上是一个 BuildingID,在所有四个表中都是唯一的。
  • 至少,如果您有四个单独的 FK,您可以使用外键约束设置并强制执行正确的引用完整性 - 如果您有一个列引用四个不同表之一,则无法实现这一点... .
  • 可能相关且有帮助:stackoverflow.com/a/5001664/44853

标签: sql-server database database-design relational-database database-schema


【解决方案1】:

您可能需要考虑类型/子类型数据模型。这非常类似于面向对象编程中的类/子类,但实现起来更加尴尬,并且没有 RDBMS(我知道)原生支持它们。总体思路是:

  • 您定义一个类型(建筑),为它创建一个表,并给它一个主键
  • 您定义两个或多个子类型(此处为医院、诊所、学校、大学),为每个子类型创建表,创建主键……但主键也是引用 Building 表的外键
  • 现在可以使用外键将包含一个“ObjectType”列的表构建到 Building 表中。你必须加入几张桌子才能确定它是什么类型的建筑,但无论如何你都必须这样做。或者存储冗余数据。

您已经注意到这个模型的问题,对吧?什么可以防止建筑物在两个或多个子类型表中出现条目?很高兴你问:

  1. 向 Building 添加一列,可能是“BuildingType”,例如 char(1),其中允许的值 {H, C, S, U} 表示 (duh) 建筑物类型。
  2. 在 BuildingID + BuildingType 上构建唯一约束
  3. 在子表中有 BulidingType 列。对其设置检查约束,使其只能设置为值(H 表示 Hospitals 表等)。理论上,这可能是一个计算列;实际上,由于以下步骤,这将不起作用:
  4. 构建外键以使用两列关联表

瞧:给定一个类型为 H 的 BUILDING 行集,不能将 SCHOOL 表中的条目(类型为 S)设置为引用该建筑物

你会记得我确实说过很难实施。

事实上,最大的问题是:这值得做吗?如果将四种(或更多,随着时间的推移)建筑类型实现为类型/子类型是有意义的(进一步的规范化优势:每个建筑物共有的地址和其他属性的一个位置,建筑物特定的属性存储在子表中),可能值得付出额外的努力来构建和维护。如果不是,那么您又回到了第一个问题:一个在现代 RDBMS 中难以实现的逻辑模型。

【讨论】:

  • +1 很好的答案,除了强制排他性(如你所解释的)之外,还可以声明式强制存在 i> 的孩子,但这需要a lot of gymnastics with circular and deferred FKs 并且可能不值得付出努力。
  • 仅使用传统的声明性参照完整性工具,我认为不可能强制子条目的存在。您可以插入建筑物,并且永远不要创建子条目。在极端情况下,您可以在 BUILDING.BuildingType 上放置一个检查约束(或查找表 + 外键),并添加一个触发器,该触发器始终在相应的子表中创建一个默认行,正如您所说,这似乎有点矫枉过正。困难、尴尬,可能是主要供应商没有开始实施它的原因。
  • 假设您将 deferred 约束包含在“常规”完整性约束的定义中,则可以纯粹通过声明性方式强制 child 的存在(看看我的链接)。我不认为这应该在实践中完成,但这是可能的。
  • 是的。另一个选项(至少在 SQL Server 中)是 INSTEAD OF 触发器。为每个子表定义一个,并在子表上执行插入。不强制存在子类型,但更容易输入它们。
【解决方案2】:

让我们从概念层面开始。如果我们将医院、诊所、学校和大学视为主题实体的类,是否存在概括所有这些的超类?大概有。我不会试图告诉你它是什么,因为我不像你那样理解你的主题。但我将继续,就好像我们可以将它们都称为“机构”,并将这四个中的每一个都视为机构的子类。

正如其他响应者所指出的,类/子类扩展和继承并未内置到大多数关系数据库系统中。但是,如果您知道正确的流行语,就会有很多帮助。以下内容旨在用数据库术语向您介绍流行语。这里是对即将到来的流行语的总结:“ER 泛化”、“ER 专业化”、“单表继承”、“类表继承”、“共享主键”。

保持在概念级别,ER 建模是在概念级别理解数据的好方法。在 ER 建模中,有一个概念“ER 泛化”和一个对应的概念“ER 专业化”,它们与我在上面作为“超类/子类”提出的思维过程平行。 ER 专业化告诉您如何绘制子类,但不会告诉您如何实现它们。

接下来我们从概念层面向下移动到逻辑层面。我们用关系或 SQL 表来表达数据。有几种实现子类的技术。一种叫做“单表继承”。另一种称为“类表继承”。与类表继承有关,还有另一种名为“共享主键”的技术。

在您使用类表继承的情况下,我们首先设计一个名为“Institutions”的表,其中包含一个 Id 字段、一个名称字段以及与机构相关的所有字段,无论它们是四种中的哪一种是。例如,邮寄地址字段之类的东西。同样,您比我更了解您的数据,并且您可以找到所有四个现有表中的字段。我们以通常的方式填充 id 字段。

接下来我们设计了四个表,分别称为“Hospitals”、“Clinics”、“Schools”和“Universities”。这些将包含一个 id 字段,以及仅与该类型机构相关的所有数据字段。例如,医院可能有“床位容量”。同样,您比我更了解您的数据,并且您可以从现有表中未进入机构表的字段中找出这些数据。

这就是“共享主键”的用武之地。当一个新条目进入“机构”时,我们必须在四个专门的子类表之一中创建一个新的并行条目。但是我们不使用某种自动编号功能来填充 id 字段。相反,我们将“机构”表中 id 字段的副本放入子类表的 id 字段中。

这是一项小工作,但其好处是值得付出努力的。共享主键强制子类条目和超类条目之间关系的一对一性质。它使连接超类数据和子类数据变得简单、容易和快速。它不需要一个特殊的字段来告诉你给定机构属于哪个子类。

而且,就您而言,它为您的原始问题提供了方便的答案。您最初询问的外键现在始终是机构表的外键。而且,由于 shared-primary-key 的魔力,外键还引用了相应子类表中的条目,而无需额外的工作。

为方便起见,您可以创建四个视图,将机构数据与四个子类表中的每一个相结合。

在 Web 和 SO 中查找“ER 专业化”、“类表继承”、“共享主键”,也许还有“单表继承”。 SO中的大多数概念或技术都有标签。

【讨论】:

    【解决方案3】:

    您可以在表上放置一个触发器并在那里强制执行参照完整性。我不认为有一个非常好的开箱即用的功能来实现这个要求。

    【讨论】:

      猜你喜欢
      • 2012-06-18
      • 2018-03-25
      • 1970-01-01
      • 1970-01-01
      • 2016-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多