设计 SQL 数据库来表示 OO 类层次结构

Posted

技术标签:

【中文标题】设计 SQL 数据库来表示 OO 类层次结构【英文标题】:Designing SQL database to represent OO class hierarchy 【发布时间】:2010-08-05 09:41:05 【问题描述】:

我正在将类层次结构转换为存储在 SQL 数据库中。

代码:

abstract class Note

   int id;
   string message;
;

class TimeNote : public Note

   time_t time;
;

class TimeRangeNote : public Note

   time_t begin;
   time_t end;
;

class EventNote : public Note

   int event_id;
;

// More classes deriving from Note excluded.

目前我有几个想法如何将其存储在数据库中。

A.将所有笔记存储在一个宽表中

该表将包含派生自Note 的所有类所需的所有信息。

CREATE TABLE t_note(
   id INTEGER PRIMARY KEY,
   message TEXT,
   time DATETIME,
   begin DATETIME,
   end DATETIME,
   event_id INTEGER
);

Note 派生的未来类需要向此表添加新列。

B.将每个类映射到一个表

CREATE TABLE t_note(
   id INTEGER PRIMARY KEY,
   message TEXT
);

CREATE TABLE t_timenote(
   note_id INTEGER PRIMARY KEY REFERENCES t_note(id),
   time DATETIME
);

CREATE TABLE t_timerangenote(
   note_id INTEGER PRIMARY KEY REFERENCES t_note(id),
   begin DATETIME,
   end DATETIME
);

CREATE TABLE t_eventnote(
   note_id INTEGER PRIMARY KEY REFERENCES t_note(id),
   event_id INTEGER
);

Note 派生的未来类需要创建一个新表。

C.使用数据库规范化和VARIANT/SQL_VARIANT

CREATE TABLE t_note(
   id INTEGER PRIMARY KEY,
   message TEXT
);

CREATE TABLE t_notedata(
   note_id INTEGER REFERENCES t_note(id),
   variable_id TEXT, -- or "variable_id INTEGER REFERENCES t_variable(id)".
                     -- where t_variable has information of each variable.
   value VARIANT
);

Note 派生的未来类需要添加新的variable_id

D.将每个具体类映射到一个表 (根据当前答案新添加)

CREATE TABLE t_timenote(
   id INTEGER PRIMARY KEY,
   message TEXT,
   time DATETIME
);

CREATE TABLE t_timerangenote(
   id INTEGER PRIMARY KEY,
   message TEXT,
   begin DATETIME,
   end DATETIME
);

CREATE TABLE t_eventnote(
   id INTEGER PRIMARY KEY,
   message TEXT,
   event_id INTEGER
);

Note 派生的未来类需要创建一个新表。


SQL 中最合乎逻辑的表示是什么? 有没有更好的选择?

【问题讨论】:

在早期设计阶段,我喜欢将我的名词等同于类 表,而将动词等同于方法......这就是选项 B 所描述的。这似乎是做事最自然的方式。 【参考方案1】:

一般来说,我更喜欢 obtion "B"(即一个表用于基类,一个表用于每个“具体”子类)。

当然,这有几个缺点:首先,当您必须读取子类的完整实例时,您必须连接至少 2 个表。此外,任何必须对任何类型的笔记进行操作的人都将不断访问“基础”表。

但这通常是可以接受的,除非您遇到极端情况(数十亿行、需要非常快的响应时间等等)。

还有第三种可能的选择:将每个子类映射到不同的表。这有助于对对象进行分区,但通常会花费更多的开发精力。

完整的讨论请参见this

(关于您的“C”解决方案,使用 VARIANT:我无法评论优点/缺点,因为它看起来像一个专有解决方案 - 它是什么?Transact-SQL?我不熟悉它)。

【讨论】:

sql_variant 是 Microsoft SQL Server 的一种数据类型。我不想使用它。 我倾向于选项 B。将每个子类映射到不同的表听起来很有趣。一个缺点是它会阻止数据库中的其他表引用任何注释(即 REFERENCES t_note(id))。另一个缺点是,正如您所说,它的开发成本更高。 +1 最逻辑的事情是B。最合乎逻辑是因为从概念上讲它是最正确的方法;其余的作为物理设计是可以接受的,当您对数据库设计进行非规范化以在某些情况下获得更好的性能时(换句话说,您正在映射对象这一事实并没有真正使其成为一个特定问题,它属于一般数据库非规范化)。 B 是唯一的关系选项。我不认为 INNER JOIN 是复杂的 SQL,只是 SQL...【参考方案2】:

您所描述的“B”选项几乎是“对象子类层次结构”的实现(Kung,1990 http://portal.acm.org/citation.cfm?id=79213)

因此,这是一种成熟且易于理解的方法。它工作得很好。如果需要,它还可以通过多级继承进行扩展。

如果您不通过 DBMS 界面限制谁可以访问数据,那么您当然会失去封装和信息隐藏的一些好处。

但是,您可以同时从多个系统甚至语言(例如 Java、C++、C#)访问它 (这是我硕士论文的主题:)

【讨论】:

【参考方案3】:

您已经了解了将对象建模到关系数据库中的 3 种最普遍接受的方法。这三个都可以接受,每个都有自己的优点和缺点。不幸的是,这意味着没有简单的“正确”答案。我已经在不同的时间实现了其中的每一个,这里有一些注意事项/注意事项要牢记:

选项 A 的缺点是,当您添加新的子类时,您必须修改现有的表(这对您来说可能比添加新表更不受欢迎)。它还有一个缺点,即许多列将包含 NULL。然而,现代数据库在管理空间方面似乎比旧数据库好得多,所以我从不担心空值。一个好处是您的任何搜索或检索操作都不需要 JOIN 或 UNION,这意味着可能会获得更好的性能和更简单的 SQL。

选项 B 的缺点是,如果向超类添加新属性,则需要向每个子类的表中添加新列。此外,如果您想进行异构搜索(一次所有子类),则必须使用 UNION 或 JOIN(可能会降低性能和/或更复杂的 sql)。

选项 C 的缺点是所有检索操作(即使只是一个子类)都将涉及 JOIN,大多数搜索也是如此。此外,所有插入都将涉及多个表,使得 SQL 更加复杂,并且需要使用事务。从数据规范化的角度来看,这个选项似乎是最“纯粹”的,但我很少使用它,因为每次操作都加入 JOIN 的缺点通常会使其他选项之一更受欢迎。

【讨论】:

抱歉,刚刚注意到这一点 - “如果您向超类添加新属性,则需要向每个子类的表添加新列”是什么意思?在 B 中,您可以将其添加到更抽象的类中,而无需添加到更具体的表中。如果您向 t_note 添加“评级”字段,则无需将其添加到其他子表中,即可返回原始状态。我错过了什么吗?【参考方案4】:

我自己更喜欢选项 A。

这也取决于您的使用场景,例如您是否需要对所有类型的笔记进行大量搜索?如果是,那么您可能会选择 A 更好。

您始终可以将它们存储为选项 A(一个大表),并根据需要为不同的子注释创建视图。这样,您仍然可以在具有良好可搜索性的同时进行逻辑分离。

一般来说,但这可能接近于宗教讨论,所以要小心,我认为关系数据库应该是关系数据库,而不是试图模仿 OO 结构。让你的类做面向对象的东西,让数据库是相关的。如果您想将其扩展到您的数据存储,可以使用特定的 OO 数据库。这确实意味着您必须克服他们所说的“对象关系阻抗不匹配”,但同样有用于该特定目的的 ORM 映射器。

【讨论】:

【参考方案5】:

我会选择选项 A

如果类层次结构非常复杂,有几十个类相互继承,则解决方案 B 很好。这是最具可扩展性的解决方案。但是,缺点是它使 SQL 更复杂和更慢。

对于比较简单的情况,比如 4 或 5 个类都继承同一个基类,选择方案 A 更有意义。SQL 会更简单更快。并且具有 NULL 值的附加列的开销可以忽略不计。

【讨论】:

【参考方案6】:

我已经使用了多年的一系列模式,统称为“Crossing Chasms”。不要让对 Smalltalk 的引用让你失望——它适用于任何面向对象的语言。尝试以下参考:

A Pattern Language for Relational Databases and SmalltalkCrossing Chasms - The Static PatternsCrossing Chasms - The Architectural Patterns

分享和享受。

编辑

Wayback Machine 链接到我在 Crossing Chasms 模式中找到的所有内容: http://web.archive.org/web/20040604122702/http://www.ksccary.com/article1.htm http://web.archive.org/web/20040604123327/http://www.ksccary.com/article2.htm http://web.archive.org/web/20040604010736/http://www.ksccary.com/article5.htm http://web.archive.org/web/20030402004741/http://members.aol.com/kgb1001001/Chasms.htm http://web.archive.org/web/20060922233842/http://people.engr.ncsu.edu/efg/591O/s98/lectures/persistent-patterns/chasms.pdf http://web.archive.org/web/20081119235258/http://www.smalltalktraining.com/articles/crossingchasms.htm http://web.archive.org/web/20081120000232/http://www.smalltalktraining.com/articles/staticpatterns.htm

我创建了一个 Word 文档,它将上述所有内容整合成一个类似连贯整体的东西,但我没有服务器可以将它放到公开可用的地方。如果有人可以建议一个免费的文档存储库,我很乐意将文档放在那里。

【讨论】:

以上链接失效【参考方案7】:

我知道这个问题很老,但我有另一个选择:

您可以将 Note 对象或 Note 对象集合作为 json 结构存储在任何表列(文本类型)中。您可以使用Newtonsoft 序列化和反序列化 json。你需要specifies type name handling options to Object for the JsonSerializer。

【讨论】:

以上是关于设计 SQL 数据库来表示 OO 类层次结构的主要内容,如果未能解决你的问题,请参考以下文章

设计模式---组合设计模式

开放/封闭原则 OO 类设计

OO2021

面向对象分析,设计,开发(OOAOODOOP)

OO第三单元总结

OO第三单元总结