您如何在数据库中有效地建模继承?


131

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

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

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


14
如果您对“最佳实践”感兴趣,那么大多数答案都是错误的。最佳实践表明RDb和应用程序是独立的;他们有完全不同的设计标准。因此,在数据库中“建模继承”(或对RDb进行建模以适合单个应用程序或应用程序语言)是一种非常糟糕的做法,不了解情况,它破坏了RDb的基本设计规则,并使其瘫痪。
PerformanceDBA 2010年


6
@PerformanceDBA那么您对避免在DB模型中继承有何建议?假设我们有50位不同类型的教师,并且我们希望将该特定教师与班级联系起来。在没有继承的情况下如何实现?
svlada 2015年

1
@svlada。这很容易在RDb中实现,因此需要“继承”。提出一个问题,包括表defns和一个示例,我将详细回答。如果您用面向对象的术语来做,那将是一团糟。
PerformanceDBA

Answers:


162

有几种方法可以对数据库中的继承进行建模。选择哪种取决于您的需求。以下是一些选择:

每种类型的表格(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(人员)的任何行,开始日期将始终为null。

逐表(TPC)

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

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

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

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate

23
“您选择哪种取决于您的需求”-请详细说明,因为我认为选择的原因构成了问题的核心。
Alex

12
看到我对这个问题的评论。为现有的Rdb技术术语使用有趣的新名称会导致混乱。“ TPT”是超型-亚型。“ TPH”未标准化,严重错误。“ TPH”的标准化程度更低,这是另一个重大错误。
PerformanceDBA 2010年

45
只有DBA会假定非规范化始终是一个错误。:)
Brad Wilson

7
尽管我将承认,在某些情况下,非规范化会导致性能提高,但这完全是由于DBMS中数据的逻辑和物理结构之间的分隔不完整(或不存在)所致。不幸的是,大多数商业DBMS都遭受这个问题的困扰。@PerformanceDBA是正确的。归一化不足是一种判断错误,会牺牲数据一致性以提高速度。遗憾的是,如果设计得当,则DBA或开发人员将永远不需要做出选择。出于记录,我不是DBA。
肯尼斯·科克伦

6
@布拉德·威尔逊 只有开发人员会出于“性能”或其他原因而将规范化。通常,这不是非标准化的,实际上是非标准化的。归一化或未归一化是一个错误,是一个事实,有理论支持,并有成千上万的经历,因此这不是“假设”。
PerformanceDBA

133

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

如果您打算将数据库用于除简单地序列化对象(例如报表,查询,多应用程序使用,商业智能等)以外的其他用途,那么我建议您不要使用从对象到表的任何简单映射。

许多人认为数据库表中的一行是一个实体(我花了很多年思考这些术语),但是一行不是一个实体。这是一个命题。数据库关系(即表)表示有关世界的一些事实陈述。该行的存在指示事实为真(反之,该行的不存在指示事实为假)。

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

最好问自己,您要存储哪些事实,您要对哪些问题进行解答,您要生成哪些报告。

一旦创建了正确的数据库设计,创建查询/视图就很简单了,该查询/视图使您可以将对象序列化为这些关系。

例:

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

若要查看区别,请考虑以下两个查询。(1)Jane Doe明年有几家酒店预订?(2)4月10日,海景旅馆预订了多少房间?

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

在关系数据库系统中,两个查询都将检查保留关系以获取其编号,并且从概念上讲,无需打扰任何其他“实体”。

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


12
+1最后,一个无知之海中的真实知识之岛(并且拒绝学习其境界之外的任何事物)。同意,这不是魔术:如果使用RDb原理设计RDb,则可以轻松地“映射”或“投影”任何“类”。将RDb强制为基于类的要求是完全不正确的。
PerformanceDBA 2010年

2
有趣的答案。您如何建议在接受的答案中对“人-雇员”示例进行建模?
sevenforce 2014年

2
@sevenforce-数据库设计实际上取决于系统的要求,未给出。几乎没有足够的信息来决定。在许多情况下,如果不盲从,类似于“每类型表”设计的内容可能是合适的。例如,开始日期可能是Employee对象拥有的一个很好的属性,但是在数据库中它实际上应该是“就业”表中的一个字段,因为一个人可以被多次雇用并具有多个开始日期。这对于对象(将使用最新的对象)无关紧要,但是在数据库中很重要。
Jeffrey L Whitledge,2014年

2
当然,我的问题主要是关于建模继承的方式。抱歉,不清楚。谢谢。正如您提到的,很可能应该有一个Employment表格,该表格收集所有就业及其开始日期。因此,如果知道a的当前雇用开始日期Employer很重要,那么这可能是a的正确用例,该a View通过查询包括该属性?(注意:似乎是因为我的昵称之后出现了“-”,我对您的评论没有任何通知)
sevenforce 2014年

5
这是答案的真正瑰宝。真正需要花一些时间,需要做些正确的练习,但这已经影响了我对关系数据库设计的思考过程。
MarioDS '16

9

简短的答案:你不知道。

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

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


3
+1尝试对数据库中的继承进行建模会浪费大量的相关资源。
Daniel Spiewak

7

正如Brad Wilson所言,TPT,TPH和TPC模式是您的选择。但有几点注意事项:

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

  • 在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;

4

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

每个实体的表是您拥有基础实体表的地方,该基础实体表具有所有子类的共享属性。然后,每个子类都有另一个表,每个表仅具有适用于该类的属性。他们的PK链接为1:1

替代文字

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

替代文字 SessionTypeID是鉴别符

每个层次结构的目标都可以更快地查询,因为您不需要联接(只需区分值),而每个实体的目标则需要进行复杂的联接才能检测出某种类型的东西并检索其所有数据。

编辑:我在这里显示的图像是我正在处理的项目的屏幕截图。资产图片不完整,因此是空的,但主要是为了显示其设置方式,而不是放在表中的内容。那取决于你 ;)。会话表保存虚拟协作会话信息,并且可以是几种会话类型,具体取决于所涉及的协作类型。


我也认为每个具体类的Target不能很好地建模继承,因此我没有展示。
mattlant

您能在插图的来源处添加参考吗?
chryss

您的答案结尾处谈论的图像在哪里?
Musa Haidari 2014年

1

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


2
人们为什么认为规范化数据库会降低性能?人们还认为DRY原理会降低代码性能吗?这种误解是从哪里来的?
史蒂文·劳

1
相对而言,可能由于非规范化可以提高性能,因此规范化会使性能降低。不能说我同意,但这可能就是它的来由。
马修·沙利

2
刚开始时,规范化对性能的影响可能很小,但是随着时间的流逝,随着行数的增加,有效的JOIN将开始胜过笨重的表。当然,标准化还有其他更大的好处-一致性和缺乏冗余性等
Rob

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

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

似乎有点杀伤力,但这全取决于您要使用它的目的!


最终的讨论是关于在每个表中添加几列,而不是关于建模继承。我认为应该更改讨论的标题,以更好地反映问题和讨论的性质。
甚至Mien

1

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

我经历过的一个是使用单表,并且有一个可区分的列。例如,Sheep数据库(没有玩笑!)将所有Sheep都存储在一个表中,而Rams和Ewes是使用该表中的“性别”列进行处理的。

因此,您可以查询所有绵羊,并获取所有绵羊。或者,您可以仅按Ram查询,它将仅获取Rams。您还可以做一些事情,例如建立只能是公羊的关系(即绵羊的父亲)等等。


1

请注意,某些数据库引擎已经像Postgres这样就已经提供了继承机制。查看文档

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

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

  / *仅显示所有员工的开始日期* /
  SELECT startdate FROM Employee;

因为这是您数据库的选择,所以您不必特别聪明!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.