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

Posted

技术标签:

【中文标题】您如何有效地为数据库中的继承建模?【英文标题】:How do you effectively model inheritance in a database? 【发布时间】:2010-09-16 11:01:57 【问题描述】:

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

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

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

【问题讨论】:

如果您对“最佳实践”感兴趣,那么大多数答案都是不正确的。最佳实践要求 RDb 和应用程序是独立的;他们有完全不同的设计标准。因此,在数据库中“建模继承”(或建模 RDb 以适应单个应用程序或应用程序语言)是一种非常糟糕的做法,不了解情况,并且违反了基本的 RDb 设计规则,并削弱了它。 Something like inheritance in database design 的可能重复项 @PerformanceDBA 那么您有什么建议避免在 DB 模型中继承?假设我们有 50 位不同类型的老师,并且我们希望将那个特定的老师与班级联系起来。如果没有继承,您将如何实现这一目标? @svlada。这很容易在 RDb 中实现,因此需要“继承”。问一个问题,包括表defns和一个例子,我会详细回答。如果你用 OO 术语来做,那将是一团糟。 How can you represent inheritance in a database?的可能重复 【参考方案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(人)的行,开始日期将始终为空。

每混凝土表 (TPC)

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

鉴于上面的类,您最终会得到这些表:

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

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

【讨论】:

'你选择哪一个取决于你的需要' - 请详细说明,因为我认为选择的原因是问题的核心。 查看我对这个问题的评论。对已经存在的 Rdb 技术术语使用有趣的新名称会导致混淆。 “TPT”是超类型-子类型。 “TPH”是非标准化的,一个严重的错误。 “TPH”的标准化程度更低,这是另一个严重错误。 只有 DBA 会认为非规范化总是错误的。 :) 虽然我承认非规范化在某些情况下会带来性能提升,但这完全是由于 DBMS 中数据的逻辑和物理结构之间的不完全(或不存在)分离。不幸的是,大多数商业 DBMS 都存在这个问题。 @PerformanceDBA 是正确的。欠规范化是一种判断错误,为了速度而牺牲数据一致性。可悲的是,如果 DBMS 设计得当,DBA 或开发人员永远不需要做出这样的选择。郑重声明,我不是 DBA。 @Brad Wilson。只有开发人员会去规范化、“为了性能”或其他方式。通常,它不是非规范化,事实是它是非规范化的。去规范化或非规范化是一个错误,是一个事实,有理论支持,有数百万人经历,这不是“假设”。【参考方案2】:

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

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

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

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

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

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

例子:

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

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

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

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

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

【讨论】:

+1 最后,一个真正知识的孤岛,在无知的海洋中(拒绝学习他们范围之外的任何东西)。同意,这不是魔术:如果 RDb 是使用 RDb 原则设计的,那么“映射”或“投影”任何“类”就很容易。将 RDb 强制为基于类的需求是不正确的。 +1,很好的答案。无论如何,您能否提供一些事实示例以及它们如何不是实体?我仍然很难找出“事实”和实体之间的区别。谢谢 有趣的答案。您如何建议在接受的答案中对 Person-Employee 示例进行建模? @sevenforce-DB设计真的要看系统的需求,没有给出。几乎没有足够的信息来决定。在许多情况下,类似于“每个类型的表格”设计的东西可能是合适的,如果不是盲目地遵循的话。例如,开始日期对于 Employee 对象来说可能是一个很好的属性,但在数据库中,它实际上应该是就业表中的一个字段,因为一个人可以被多次雇用,具有多个开始日期。这对于对象(将使用最新的)无关紧要,但在数据库中很重要。 这是一个真正的答案。这需要一些时间才能真正融入并需要一些练习才能正确,但它已经影响了我对关系数据库设计的思考过程。【参考方案3】:

简短的回答:你不知道。

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

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

【讨论】:

+1 尝试在数据库中对继承建模是对良好关系资源的浪费。【参考方案4】:

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

【讨论】:

【参考方案5】:

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

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

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

SessionTypeID 是判别器

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

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

【讨论】:

我还认为 Target per Concrete class 不能很好地模拟继承,所以我没有展示。 你能添加一个插图来自的参考吗? 你在回答最后提到的图片在哪里?【参考方案6】:

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

【讨论】:

为什么人们认为规范化数据库会降低性能?人们是否还认为 DRY 原则会降低代码性能?这种误解从何而来? 可能是因为反规范化可以提高性能,因此规范化会降低性能,相对而言。不能说我同意,但它可能就是这样产生的。 一开始,规范化可能对性能的影响很小,但随着时间的推移,随着行数的增加,高效的 JOIN 将开始胜过更庞大的表。当然,规范化还有其他更大的好处 - 一致性和缺乏冗余等。【参考方案7】:

repeat of similar thread answer

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

例如

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 表,列出表之间的继承关系,并指定实现每个表行为的程序集和类名

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

【讨论】:

讨论最终是关于向每个表添加几列,而不是关于建模继承。我认为应该更改讨论的标题,以更好地反映问题和讨论的性质。【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

请注意,一些数据库引擎已经原生提供了继承机制,例如Postgres。看documentation。

例如,您可以像这样查询上述响应中描述的人员/员工系统:

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

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

【讨论】:

以上是关于您如何有效地为数据库中的继承建模?的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地为 sql 中的数据类型分配大小?

如何有效地为列表列表中的多个补丁设置动画

如何有效地为 Java 中的单链表节点实现 hashCode()?

如何在关系数据库中进行继承建模?

有效地管理多个相同的数据库? [关闭]

如何有效地为许多产品提供 1 个 html 布局页面,这些产品将调用 mysql 数据库获取产品信息并插入我指定的位置?