158

在数据库中建模继承的最佳实践是什么?

有哪些权衡(例如可查询性)?

(我对 SQL Server 和 .NET 最感兴趣,但我也想了解其他平台如何解决这个问题。)

4

9 回答 9

187

有几种方法可以在数据库中对继承进行建模。您选择哪种取决于您的需求。这里有几个选项:

每种类型的表 (TPT)

每个班级都有自己的桌子。基类包含所有基类元素,从它派生的每个类都有自己的表,主键也是基类表的外键;派生表的类只包含不同的元素。

例如:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

会产生如下表格:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

逐层表 (TPH)

有一个表代表所有继承层次结构,这意味着其中的几列可能是稀疏的。添加了一个鉴别器列,它告诉系统这是什么类型的行。

给定上面的类,你最终得到这个表:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

对于行类型为 0(人)的任何行,开始日期将始终为空。

每混凝土表 (TPC)

每个类都有自己的完整表格,没有对任何其他表格的引用。

给定上面的类,你最终得到这些表:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
于 2008-10-10T06:11:28.853 回答
162

正确的数据库设计与正确的对象设计完全不同。

如果您打算将数据库用于除了简单地序列化您的对象(例如报告、查询、多应用程序使用、商业智能等)之外的任何事情,那么我不推荐任何类型的从对象到表的简单映射。

许多人将数据库表中的一行视为一个实体(我花了很多年的时间思考这些术语),但一行不是实体。这是一个提议。数据库关系(即表)表示关于世界的一些事实陈述。该行的存在表明事实是真实的(相反,它的缺失表明事实是错误的)。

通过这种理解,您可以看到面向对象程序中的单个类型可能跨十几个不同的关系存储。并且多种类型(通过继承、关联、聚合或完全不从属)可以部分存储在单个关系中。

最好问问自己,你想存储什么事实,你想回答什么问题,你想生成什么报告。

一旦创建了正确的数据库设计,那么创建允许您将对象序列化到这些关系的查询/视图就很简单了。

例子:

在酒店预订系统中,您可能需要存储 Jane Doe 在 Seaview Inn 预订 4 月 10 日至 12 日房间的事实。这是客户实体的属性吗?它是酒店实体的属性吗?它是一个预订实体,其属性包括客户和酒店吗?它可以是面向对象系统中的任何或所有这些东西。在数据库中,这些都不是。这只是一个赤裸裸的事实。

要查看差异,请考虑以下两个查询。(1) Jane Doe 明年有多少酒店预订?(2) 海景客栈4月10日订了多少房?

在面向对象的系统中,查询(1)是客户实体的属性,查询(2)是酒店实体的属性。这些对象会在其 API 中公开这些属性。(不过,显然获得这些值的内部机制可能涉及对其他对象的引用。)

在关系数据库系统中,两个查询都将检查预留关系以获取它们的数字,并且从概念上讲,不需要打扰任何其他“实体”。

因此,通过尝试存储有关世界的事实——而不是尝试存储具有属性的实体——来构建适当的关系数据库。一旦设计得当,就可以轻松构建在设计阶段没有想到的有用查询,因为完成这些查询所需的所有事实都在适当的位置。

于 2008-10-10T22:18:55.787 回答
11

简短的回答:你没有。

如果您需要序列化您的对象,请使用 ORM,甚至更好的东西,如 activerecord 或 prevaylence。

如果您需要存储数据,请以关系方式存储它(注意存储的内容,并注意 Jeffrey L Whitledge 刚才所说的内容),而不是受对象设计的影响。

于 2008-10-10T22:26:10.450 回答
10

正如 Brad Wilson 所提到的,TPT、TPH 和 TPC 模式是你要走的路。但有几点注意事项:

  • 从基类继承的子类可以看作是数据库中基类定义的弱实体,这意味着它们依赖于它们的基类,没有它就不能存在。我已经多次看到,为每个子表存储唯一 ID,同时将 FK 保留到父表。一个 FK 就足够了,它甚至更好地为子表和基表之间的 FK 关系启用 on-delete 级联。

  • 在 TPT 中,仅通过查看基表记录,您无法找到该记录所代表的子类。当您想要加载所有记录的列表时(无需 select 对每个子表执行操作),有时需要这样做。处理此问题的一种方法是使用一列表示子类的类型(类似于 TPH 中的 rowType 字段),因此以某种方式混合 TPT 和 TPH。

假设我们要设计一个包含以下形状类图的数据库:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

上述类的数据库设计可以是这样的:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;
于 2014-08-22T17:37:17.167 回答
4

您可以在数据库中设置两种主要类型的继承,即每个实体的表和每个层次结构的表。

每个实体的表是您拥有一个基础实体表的地方,该表具有所有子类的共享属性。然后,每个子类都有另一个表,每个表只包含适用于该类的属性。他们通过 PK 以 1:1 的比例联系在一起

替代文字

每个层次结构的表是所有类共享一个表的地方,可选属性可以为空。它们也是一个鉴别器字段,它是一个数字,表示记录当前持有的类型

替代文字 SessionTypeID 是鉴别器

每个层次结构的目标查询速度更快,因为您不需要连接(只有鉴别器值),而每个实体的目标您需要进行复杂的连接以检测某物是什么类型并检索其所有数据。

编辑:我在这里展示的图像是我正在处理的项目的屏幕截图。资产图像不完整,因此它是空的,但它主要是为了展示它的设置,而不是在你的表格中放置什么。那取决于你 ;)。会话表保存虚拟协作会话信息,并且根据所涉及的协作类型可以是多种类型的会话。

于 2008-10-10T06:08:43.727 回答
1

您将对数据库进行规范化,这实际上会反映您的继承。它可能会降低性能,但这就是规范化的方式。您可能必须使用良好的常识来找到平衡点。

于 2008-10-10T06:06:01.860 回答
1

重复类似的线程答案

在 OR 映射中,继承映射到父表,其中父表和子表使用相同的标识符

例如

create table Object (
    Id int NOT NULL --primary key, auto-increment
    Name varchar(32)
)
create table SubObject (
    Id int NOT NULL  --primary key and also foreign key to Object
    Description varchar(32)
)

SubObject 与 Object 具有外键关系。创建 SubObject 行时,必须先创建一个 Object 行并在两行中​​使用 Id

编辑:如果您还想对行为进行建模,则需要一个列出表之间继承关系的类型表,并指定实现每个表行为的程序集和类名

看起来有点矫枉过正,但这一切都取决于你想用它做什么!

于 2008-10-10T06:13:46.277 回答
1

使用 SQL ALchemy (Python ORM),您可以进行两种类型的继承。

我有过的经验是使用单表,并有一个判别列。例如,绵羊数据库(不是开玩笑!)将所有绵羊存储在一个表中,而公羊和母羊使用该表中的性别列进行处理。

因此,您可以查询所有绵羊,并获取所有绵羊。或者你可以只通过 Ram 查询,它只会得到 Rams。你也可以做一些事情,比如有一个只能是一只公羊的关系(即,一只羊的父亲),等等。

于 2008-10-10T08:28:53.867 回答
1

请注意,一些数据库引擎已经像Postgres一样提供了原生的继承机制。查看文档

例如,您将查询上述响应中描述的人员/员工系统,如下所示:

  /* 这显示所有人员或员工的名字 */
  从人中选择名字;

  /* 这仅显示所有员工的开始日期 */
  从员工中选择开始日期;

那是你的数据库的选择,你不需要特别聪明!

于 2008-10-10T09:10:54.687 回答