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

需要进行哪些权衡(例如可查询性)?

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

评论

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

数据库设计中类似继承的可能重复对象

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

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

可能如何表示数据库中的继承?

#1 楼

有几种方法可以对数据库中的继承进行建模。选择哪种取决于您的需求。以下是一些选项:每个类型的表(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


评论


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

– Alex
09年7月7日在11:11

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

– PerformanceDBA
2010-11-10 21:33

只有DBA会认为非规范化总是一个错误。 :)

–布拉德·威尔逊(Brad Wilson)
2010年11月19日在17:04

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

–肯尼斯·科克伦(Kenneth Cochran)
13年7月26日在20:09

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

– PerformanceDBA
15年4月20日在10:37

#2 楼

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

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

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

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

最好问自己,您要存储哪些事实,您想要什么问题的答案,想要生成什么报告。

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

示例:

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

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

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

在关系数据库系统中,两个查询都将检查保留关系以获取其编号,并且从概念上讲因此,通过尝试存储有关世界的事实(而不是尝试存储具有属性的实体),可以构造一个适当的关系数据库,而无需花费任何其他“实体”的麻烦。一旦设计正确,就可以轻松构造在设计阶段未曾想到的有用查询,因为满足这些查询所需的所有事实都在其适当的位置。

评论


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

– PerformanceDBA
2010-11-10 21:38

+1,好答案。无论如何,您能否提供一些事实示例,以及它们如何不是实体?我仍然很难找出“事实”与实体之间的区别。谢谢

–fra
2011-2-14在8:12

有趣的答案。您如何建议在接受的答案中为“人-雇员”示例建模?

– Sevenforce
14-10-29在11:12

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

– Jeffrey L Whitledge
14-10-29在14:38

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

– MarioDS
16年8月16日14:59



#3 楼

简短的答案:您不需要。

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

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

评论


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

–丹尼尔·斯皮瓦克
08-10-10在22:35

#4 楼

正如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;


#5 楼

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

每个实体的表是您拥有具有所有子类的共享属性的基本实体表的地方。然后,每个子类都有另一个表,每个表仅具有适用于该类的属性。它们通过其PK's 1:1链接。每个层次结构中的表是所有类共享表的地方,并且可选属性可以为空。它们也是一个鉴别符字段,它是一个数字,表示记录当前所保存的类型。


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

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

评论


我还会考虑每个具体类的Target不能很好地建模继承,因此我没有展示。

– Matttlant
08-10-10在6:20

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

–chryss
08-10-10在7:03

您的答案结尾处谈论的图像在哪里?

–穆萨·海达里(Musa Haidari)
2014年8月26日在13:16

#6 楼

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

评论


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

–史蒂文·劳(Steven A. Lowe)
08-10-10在6:12

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

–马修·沙利(Matthew Scharley)
08-10-10在7:04

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

–Rob
08-10-10在22:24

#7 楼

重复类似的线程答案

在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

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

似乎有点过头了,但这都取决于您要使用它的目的!

评论


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

–连面
08-10-10在6:18

#8 楼

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

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

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

#9 楼

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

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

  /* This shows the first name of all persons or employees */
  SELECT firstname FROM Person ; 

  /* This shows the start date of all employees only */
  SELECT startdate FROM Employee ;


这样,您就可以选择数据库了,您不必特别聪明!