我正在研究一个精简的数据库模式(在 PostgreSQL 中),比如旧的FreeBase,只是没有那么多东西。到目前为止大约有 100 张桌子,但那是在我考虑为特殊情况添加几十张之前,感觉不太对劲。让我用一个简化的例子来解释这个问题,它以一种小的方式复制了这个问题,想象一下它会变得更加复杂,一个表有几十个可选的属性/关系/关联,并且有许多像这样的不同的互连表。
我知道并广泛使用过像 MongoDB 这样的 NoSQL 文档数据库,并且在像 Neo4j 这样的图形数据库中涉足过。由于这个副项目的复杂性,它们是我宁愿避免使用的工具,而且用于部署的工具和资源与当今世界的 PostgreSQL 之类的东西不一样。
因此,为了说明这个问题,想象一个“符号”表,其中包含所有unicode符号,以及 unicode 范围之外的数千个符号(征兵、玛雅脚本、其他非脚本符号等)。基表如下所示:
table symbols {
id
unicode (optional)
preview_image_url (optional)
title
description
}
我们已经有了一些可选属性,因为有些符号没有 unicode,有些不需要预览图像(所有 unicode 都可以在浏览器中呈现,等等)。但是,让我们考虑一下我们想要存储有关...的结构化信息的其他一些“类型”符号。
首先,我们可以想到“脚本”符号,即用于书写系统的符号。很酷,我们可以在我们的表中添加可选的“script_name”属性,这还不错。但是不,什么样的脚本符号?有从右到左的文字、垂直文字、表意文字、字母文字、abjads 和 abugidas 等。一些字母文字如拉丁文字有镜像符号(如括号)或大写/小写对。一些脚本将字符与特定规则组合在一起,这些规则可以与哪些组合。有些符号纯粹是装饰性的,有些是几何的。因此,我们尝试考虑所有这些可选功能:
table symbols {
id
unicode (optional)
preview_image_url (optional)
title
description
is_logographic (optional)
is_vertical (optional)
is_rtl (optional)
is_alphabet (optional)
is_abjad (optional)
is_abugida (optional)
script_name (optional)
mirror_image_symbol_id (optional)
uppercase_symbol_id (optional)
lowercase_symbol_id (optional)
combining_class (optional)
}
不过,有些人可能会说拥有所有这些可选属性还不错,我不知道。
然后您可以继续并添加更多子子类型....
- 类似三角形的符号(在 unicode 中有一些)
- 阴影三角形符号
- 空三角形符号
想象一下你可以尝试在谷歌上搜索与符号相关的所有可能的东西。
- 看起来像“c”的符号
- ©(版权符号)
- ?(版权符号)
- ℃(摄氏度的符号)
- ¢(美元的美分符号)
- ₡(哥斯达黎加和萨尔瓦多货币冒号的符号)
- ₵(塞地符号,加纳货币)
- ₢(克鲁塞罗的符号,巴西的历史货币)
- ℄(实际上是“cl”
- 带有内置组合标记的符号,如 é。
- 1 字节的 Unicode 字形
- 2 字节 unicode 字形
- 4字节...
- ...
它开始变成这样:
table symbols {
id
unicode (optional)
preview_image_url (optional)
title
description
is_logographic (optional)
is_vertical (optional)
is_rtl (optional)
is_alphabet (optional)
is_abjad (optional)
is_abugida (optional)
script_name (optional)
mirror_image_symbol_id (optional)
uppercase_symbol_id (optional)
lowercase_symbol_id (optional)
combining_class (optional)
is_triangle_like (optional)
is_shaded_triangle_like (optional)
is_empty_triangle_like (optional)
looks_like_c (optional)
looks_like_d (optional)
looks_like_l (optional)
...
has_built_in_diacritic (optional)
is_1_byte (optional)
is_2_bytes (optional)
...
}
很快我们就会得到 50 或 100 个可选字段。当您尝试对“生物体”及其所有独特而多样的特征进行建模时,您可以想象这会变得更加复杂!数以千计的可选功能,并且没有明确的 OO 类层次结构来创建子类,它更像是一个互连组合的图/网络。
所以我的想法开始转向让事情变得超级抽象/通用,并创建一个名为“facts”的表格,比如:
table facts {
id
object_type
object_id
property_name
value_type
value_id
}
这样你就可以创建一个像“symbol a”这样的对象,并在它上面有“facts”,比如“属性名称是script_name
,值类型是一个字符串表,其中一个字符串映射到一个ID,作为对象符号类型的属性” . 或者另一个事实是:
// facts table
id: 123
object_type: 'symbol'
object_id: 12321
property_name: 'is_1_byte'
value_type: 'boolean'
value_id: 444
// boolean table
id: 444
value: true
// symbol table
id: 12321
unicode: 'a'
但是沿着这条路走下去,你最终只会得到几个表(基本上是“事实”表,可能还有 1 或 2 个其他元表),而不是 100 个。但是事情变得更加难以思考和可视化,并且查询变得更复杂一些。
但我看不到解决这个问题的方法。我倾向于让数据库成为这种抽象类型的“事实”表,但是在应用程序层中让它看起来更面向对象,就像在 JavaScript 中一样,它有属性或没有属性。我想稍微“强化”一下,并给每个组合/变体一个不同的类型名称,但这并不完全奏效,例如:
{
type: 'alphabet_symbol',
value: 'e'
},
{
type: 'geometric_symbol',
value: '▲'
}
然后构建一个类型树:
symbol
alphabet_symbol
mirror_image_alphabet_symbol
mirror_image_alphabet_symbol_with_capital_lowercase
capital_lowercase_alphabet_symbol
abjad_symbol
geometric_symbol
triangle_geometric_symbol
shaded_triangle_geometric_symbol
但这打破了:
symbol
alphabet_symbol
(cyrillic б)
look_like_6_symbol
(cyrillic б)
所以就像,也许只是向中心对象添加标签。
б
id: 455
tags
- name: 'is_alphabet_symbol'
symbol_id: 455
- name: 'looks_like_6_symbol'
symbol_id: 455
但在这一点上,它归结为我最初在您开始尝试处理更多案件时分享的通用/抽象“事实”想法。
// facts table
id: 124
object_type: 'symbol'
object_id: 455
property_name: 'is_alphabet_symbol'
value_type: 'boolean'
value_id: 444
// boolean table
id: 444
value: true
// symbol table
id: 455
unicode: 'б'
所以想知道,这里简要概述的处理“类型”的动态性和变化的推荐方法是什么?您如何平衡捕获尽可能多的结构化数据的愿望而不制作一个大的可选填充平面表(这似乎在几十个可选列之后分解,更不用说在有机体建模的情况下为 100 或 1000) .
通常,您拒绝向模型添加如此多的属性,并规范化模型。对于每个属性,请询问 1) 是否真的需要添加到您的数据模型中,以及 2) 它是否真的属于该实体的属性,或者您是否应该向模型中引入新的子类型或引用实体。
例如
is_rtl
,可能应该是相关Alphabet
实体的属性,looks_like_d
应该在单独的链接表中建模,并且is_shaded_triangle_like
可能不值得添加到数据模型中。因此模型从单个表演变为适当的模型,例如:
抽象数据库层是一个很常见的想法,但这样做会丢失数据库系统的很多关系方面(令人惊讶的是,这在关系数据库管理系统中并不是一个好主意)。这被称为EAV 反模式,通常应该避免有几个原因,其中一些包括查询的复杂性增加,正如您所注意到的,还有一些是性能问题。
在您的数据对象上拥有可为空的属性并不是世界末日,即使这会导致很多列。在 ERP 系统的数据库中看到这种情况并不少见,这些数据库通常有数千到数十万个表,有些每个表有几百到数千列。但这也不是最好的设计。
正如大卫所说,更好的解决方案是规范化您的数据对象。考虑它的一个好方法是重构除对象表示的每个(或大多数)记录共有的核心属性之外的任何内容。将其他相关属性分组到自己的表中。这确实可能会导致更多的表,但如前所述,许多表在功能上没有任何问题。当然,维护工作量更大,但这是对适当设计的关系模式的权衡,有利于提高性能(否则本身可能需要大量工作)、改进的相关性以及改进的数据管理和准确性。