设计SQL数据库以表示面向对象类层次结构

16

我正在将一个类层次结构转换为存储在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所描述的。这似乎是最自然的做法。 - ravibhagw
7个回答

9
一般来说,我更喜欢选项 "B"(即为基类创建一个表格,为每个“具体”子类创建一个表格)。
当然,这有一些缺点:首先,每当您需要读取子类的完整实例时,您都必须加入至少2个表格。此外,“基础”表格将被任何需要操作任何类型的笔记的人不断访问。
但是,除非您有极端情况(数十亿行,需要非常快的响应时间等),否则通常可以接受。
第三种可能的选择是将每个子类映射到单独的表格。这有助于对对象进行分区,但通常在开发工作量方面成本更高。
请参见此处进行完整讨论。
(关于您的“C”解决方案,使用VARIANT:我无法评论其优缺点,因为它看起来像是专有解决方案-它是什么?Transact-SQL吗?而且我不熟悉它。)

sql_variant 是 Microsoft SQL Server 的一种数据类型。我宁愿不使用它。 - dalle
1
我倾向于选项B。将每个子类映射到不同的表听起来很有趣。一个缺点是它会阻止数据库中的其他表引用任何笔记(即REFERENCES t_note(id))。另一个缺点是,正如你所说,它在开发工作方面的成本更高。 - dalle
+1 最合理的事情是B。概念上来说,这是最正确的方法;其余的方案可以被接受为物理设计,在数据库设计中去除冗余以达到特定场景的更好性能(换句话说,你映射对象并不是一个特定的问题,它属于通用的数据库去冗余)。 - Unreason
B是唯一的关系选项。我不认为INNER JOIN是复杂的SQL,只是SQL... - Charles Robertson

3
你所描述的“B”选项基本上是实现“对象子类层次结构”的一种方法(Kung, 1990 http://portal.acm.org/citation.cfm?id=79213)。因此,这是一种已经被广泛认可和理解的方法,它的效果非常好。如果需要,它还可以通过多级继承来进行扩展。
当然,如果你不通过DBMS接口限制可以访问数据的人员,就会失去一些封装和信息隐藏的好处。
但是,你可以同时从多个系统和语言(如Java、C++、C#)中访问它。(这是我硕士论文的主题 :)

3
你已经掌握了将对象建模成关系型数据库的三种最常见方法。这三种方法都是可以接受的,每种方法都有其优缺点。不幸的是,这意味着没有一种绝对正确的答案。我曾在不同的时间实现过这三种方法,以下是需要牢记的几个注意事项/警告:
选项A的缺点是,当你添加一个新的子类时,必须修改现有的表(这可能比添加一个新的表更难以接受)。它还有一个缺点,即许多列将包含NULL值。然而,现代数据库似乎比旧的数据库更擅长管理空间,所以我从来没有太担心nulls。一个好处是,你的所有搜索或检索操作都不需要JOIN或UNION,这意味着潜在的更好性能和更简单的SQL。
选项B的缺点是,如果你为超类添加一个新属性,你需要为每个子类的表添加一个新列。此外,如果您想进行异构搜索(同时搜索所有子类),则必须使用UNION或JOIN(潜在的更慢的性能和/或更复杂的sql)。
选项C的缺点是,所有检索操作(即使只是一个子类)都将涉及到JOIN,大多数搜索也是如此。此外,所有插入都将涉及多个表,从而使SQL稍微复杂一些,并且将需要使用事务。从数据规范化的角度来看,这种选项似乎是最“纯粹”的,但我很少使用它,因为每个操作都需要JOIN的缺点通常会使其他选项更容易接受。

抱歉,刚刚注意到这个问题 - 你说的“如果你在超类中添加一个新属性,你需要在每个子类的表中添加一个新列”是什么意思?在B中,您将其添加到更抽象的类中,无需添加到更具体的表中。回到原来的问题,如果您向t_note添加“评分”字段,则无需将其添加到其他子表中。我有什么遗漏吗? - p.marino

1

我个人倾向于选项A。

这也有点取决于你的使用场景,例如你是否需要在所有类型的笔记中进行大量搜索?如果是的话,那么你可能最好选择选项A。

你总是可以将它们存储为选项A(一个大表),并为不同的子笔记创建视图,如果你愿意的话。这样,你仍然可以在具有良好的可搜索性的同时保持逻辑分离。

一般来说,但这可能接近于宗教讨论,所以要小心,我认为一个关系型数据库应该是一个关系型数据库,而不是试图模仿OO结构。让你的类做OO的事情,让数据库保持关系。如果你想将其扩展到数据存储器,则有特定的OO数据库可用。这意味着你必须跨越所谓的“对象关系阻抗不匹配”,但是再次提醒,有ORM映射器专门用于此目的。


0

有一系列被称为“跨越鸿沟”的模式,我已经使用了很多年。不要让对Smalltalk的引用困扰你——它适用于任何面向对象的语言。请尝试以下参考:

关系数据库和Smalltalk的模式语言
跨越鸿沟-静态模式
跨越鸿沟-架构模式

分享并享受。

编辑

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 文档,将上述所有内容整合成了一个类似于连贯的整体,但我没有可以放置它以使其公开可用的服务器。如果有人能够建议一个免费的文档存储库,我很乐意将该文档上传到那里。


以上链接已失效。 - sheki

0

我会选择选项A

如果类层次结构非常复杂,有数十个类相互继承,则解决方案B是不错的选择。它是最可扩展的解决方案。然而,缺点是它使SQL更加复杂和缓慢。

对于相对简单的情况,例如4或5个类都继承相同的基类,选择解决方案A更有意义。SQL将更简单,更快。并且具有额外列的开销可以忽略不计。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接