16

我正在学习如何将实体关系图解释为 SQL DDL 语句,但我对符号的差异感到困惑。考虑如下图所示的不相交关系:

这将表示为:

  1. 车辆、2WD 和 4WD 表(2WD 和 4WD 将指向车辆的 PK);或者
  2. 只有 2WD 和 4WD 表(和 NO Vehicle 表),这两个表都会复制 Vehicle 所具有的任何属性?

我认为这些是编写关系的其他方式:

我正在寻找一个清晰的解释,说明您最终会为每个图表使用哪些表格。

4

4 回答 4

29

ER 符号

有几种 ER 符号。我不熟悉您正在使用的那个,但很明显您正在尝试表示一个子类型(又名继承、类别、子类、泛化层次结构......)。这是 OOP 继承的关系表亲。

在进行子类型化时,您通常关心以下设计决策:

  • 抽象与具体:可以实例化父级吗?在您的示例中: a 可以Vehicle存在而不存在2WDor4WD吗?1
  • 包容性 vs. 排斥性:可以为同一个父母实例化多个孩子吗?在您的示例中,可以Vehicle同时 2WDand4WD吗?2
  • 完整与不完整:您预计未来会添加更多的孩子吗?在您的示例中,您是否希望稍后可以将 aBike或 a Plane(etc...) 添加到数据库模型中?

信息工程符号区分包含和排除子类型关系。另一方面,IDEF1X 表示法并没有(直接)识别这种差异,但它确实区分了完整和不完整的子类型(IE 没有)。

ERwin 方法指南(第 5 章,子类型关系)中的下图说明了差异:

在此处输入图像描述

IE 和 IDEF1X 都不允许直接指定抽象与具体父级。

物理表示

不幸的是,实际数据库不直接支持继承,因此您需要将此图转换为真实表。通常有3种方法可以做到这一点:

  1. 将所有类放在同一个表中,并将子字段保留为 NULL。然后,您可以进行 CHECK 以确保正确的字段子集为非 NULL。
    • 优点:没有加入,所以一些查询可以受益。可以强制执行父级密钥(例如,如果您想避免不同2WD4WD车辆具有相同的 ID)。可以轻松地强制执行包容性与排他性子项以及抽象与具体父项(只需更改 CHECK)。
    • 缺点:一些查询可能会更慢,因为它们必须过滤掉“无趣”的孩子。根据您的 DBMS,特定于子的约束可能会出现问题。很多 NULL 会浪费存储空间。不太适合不完整的子类型化 - 添加新子项需要更改现有表,这在生产环境中可能会出现问题。
  2. 将所有子项放在单独的表中,但没有父项的表(相反,在所有子项中重复父项的字段和约束)。具有 (3) 的大部分特征,同时避免 JOIN,但代价是可维护性较低(由于所有这些字段和约束重复)以及无法强制执行父级键或表示具体父级。
  3. 将父母和孩子放在单独的表格中。
    • 优点:干净。不需要人为地重复任何字段/约束。强制执行父级键并轻松添加特定于子级的约束。适用于不完整的子类型(添加更多子表相对容易)。某些查询可以通过仅查看“有趣的”子表来受益。
    • 缺点:一些查询可能是 JOIN-heavy。可能难以强制执行包容性与排他性子级和抽象与具体父级(如果 DBMS 支持循环和延迟外键,则可以声明式地强制执行这些,但在应用程序级别强制执行它们通常被认为是一种较小的邪恶)。

如您所见,情况并不理想 - 无论您选择何种方法,您都需要做出妥协。方法 (3) 可能应该是您的起点,并且只有在有令人信服的理由时才选择其中一种方法。


1我猜这就是你的图表中线条的粗细。

2我猜这就是您的图表中存在或不存在“不相交”的含义。

于 2012-08-20T12:10:56.060 回答
3

通常当您在数据库设计中进行超类型/子类型关系时,您需要为您的通用实体类型(超类型)创建一个单独的表,并为您的专业实体版本(子类型)创建一个单独的表脱节与否。在您的情况下,您将需要为 VEHICLE 创建一个表和一个主键以及一些常见的或由所有子类型共享的属性。然后,您需要为 2WD 和 4WD 创建单独的表以及仅针对这些表的属性。例如

那么您可以使用 SQL 连接查询这些表

于 2012-08-20T12:30:18.003 回答
2

其他响应者所说的话,以及以下进入子类表的主键的内容。

您的案例看起来像是称为“泛化专业化”或简称 Gen-Spec 的设计模式的一个实例。如何使用数据库表对 gen-spec 建模的问题一直出现在 SO 中。

如果您在 Java 等 OOPL 中对 gen-spec 进行建模,您将使用子类继承工具来为您处理细节。您只需定义一个类来处理通用对象,然后定义一个子类集合,每个子类对应于每种类型的专用对象。每个子类都将扩展通用类。它简单明了。

不幸的是,据我所知,关系数据模型没有内置子类继承,而且 SQL 数据库系统不提供任何此类工具。但你并不是不走运。您可以设计您的表以与 OOP 的类结构相似的方式对 gen-spec 进行建模。当新项目添加到通用类时,您必须安排实现自己的继承机制。详情如下。

类结构相当简单,一个表用于 gen 类,一个表用于每个规范子类。这是一个很好的插图,来自 Martin Fowler 的网站。 类表继承。 请注意,在此图中,Cricketer 既是子类又是超类。您必须选择哪些属性放在哪些表中。该图显示了每个表中的一个示例属性。

棘手的细节是如何为这些表定义主键。gen 类表以通常的方式获取主键(除非该表是另一种泛化的特化,例如 Cricketers)。大多数设计师给主键起一个标准的名字,比如“Id”。他们使用自动编号功能来填充 Id 字段。规格类表有一个主键,可以命名为“Id”,但不使用自动编号功能。相反,每个子类表的主键都被限制为引用通用表的主键。这使得每个专用主键既是外键又是主键。请注意,对于 Cricketers,Id 字段将引用 Players 中的 Id 字段,但 Bowlers 中的 Id 字段将引用 Cricketers 中的 Id 字段。

现在,当您添加新项目时,您必须保持参照完整性,方法如下。
您首先在 gen 表中插入一个新行,为它的所有属性提供数据,但主键除外。自动编号机制生成唯一的主键。接下来,您将新行插入到适当的规范表中,包括其所有属性的数据,包括主键。您使用的主键是刚刚生成的全新主键的副本。这种主键的传播可以称为“穷人的继承”。

现在,当您想要所有通用数据以及来自一个子类的所有专用数据时,您所要做的就是通过公共键连接两个表。所有与相关子类无关的数据都将退出连接。它光滑、简单、快速。

于 2012-08-20T17:01:26.933 回答
1

实现任何特定数据模型的方法并不总是只有一种。当您从逻辑模型转移到物理模型时,通常会发生转换。

标准 SQL 没有一种干净的方式来强制执行不相交的子类型约束。

如果您的目标是使用模式尽可能多地强制执行模型的规则,那么实现模型的标准方法是为超类型使用一个表,为每个子类型使用一个表。这可确保每个实体仅使用适用的属性。

有一个或多或少标准的 SQL 技巧来执行不相交的约束。它让一些人望而却步,因为它以一种不重要的方式违反了规范化规则。尽管如此,有些人还是觉得这种技术在美学上令人反感,因为它违反了 2NF。

该技术涉及向超类型添加分区属性,并在每个子类型中包含此分区属性,将其添加到子类型的主键中。除了为分区属性强加特定值的检查约束外,这确保了每个实体最多可以有一个子类型。该技术在许多地方都有详细记录,例如这个博客

于 2012-08-20T12:30:23.093 回答