当我 10 年前加入这个行业时,我的第一个项目使用的是关系数据库。之后,我的下一个项目也使用了关系型数据库。而且,正如您可能猜到的那样,我的下一个下一个项目也使用了关系数据库。这种情况持续了很长时间,以至于我几乎忘记了表格只是一种存储数据的格式。
四年前,当我的公司慢慢转向大数据分析和知识管理时,我才发现自己对其他类型的数据库感兴趣。这些年,对RDF和Ontology的接触,让我有一种重新审视、重新思考建库的方法和原则的冲动。
在本文的范围内,我将只关注数据库模式的作用。如果您对 RDF 和 Ontology 这两个术语感到陌生,请不要担心。我将尽力在本文中介绍这些概念。
背景
图作为知识数据库
众所周知,创建关系数据库从定义数据库模式开始。不管我们选择哪个数据库,我们仍然需要在插入数据之前定义表、列和任何外键。显然,我们需要在制作数据之前决定数据的外观。
但是,在构建知识数据库时,这不是适合我们的方法。因为系统应该存储未来的知识而不是当前的知识,所以不可能弄清楚数据会是什么样子。因此,我们将关注点从关系数据库转移到其他解决方案上。
有很多NoSQL数据库可以支持无模式数据,但大多数都不符合我们的要求,因为我们要存储关联数据。因此,图数据库似乎是我们最轰动的选择。
资源描述框架
市场上逛了一圈,总有一种不安的感觉,因为查询语言没有一个被广泛采用的标准。如果我们选择图形数据库,我们最终可能会编写特定于供应商的实现,这似乎不是一个好的开始策略。
为了寻找一些通用标准,我们设法找到了资源描述框架,这是一种用于 Web 资源中信息建模的 W3C 规范。这似乎是我们最好的选择,因为资源描述框架带有一个非常简单的机制来描述资源链接。唯一的副作用是每个资源都需要由 URI 标识。
例如,为了描述 Apple 生产的 iPhone 5,售价为 600 美元,我们需要生成如下两个三元组:
<http://example.org/Apple> <http://example.org/produce> <http://example.org/iPhone5>
<http://example.org/iPhone5> <http://example.org/price> '600 USD'@en
我们对使用 URI 作为资源标识符没有兴趣,但为了符合标准仍然需要这样做。然而,我们不能责怪 RDF,因为它是为 Web 发明的。作者有一个美好的梦想,我们可以按照资源的 URI 来检索它。显然,这个想法没有实现。
撇开 RDF 最初的想法不谈,对我们来说主要的好处是查询语言 SPARQL。例如,要计算任何 Apple 手机的价格,查询应该是:
<http://example.org/Apple> <http://example.org/produce> <http://example.org/iPhone5>
<http://example.org/iPhone5> <http://example.org/price> '600 USD'@en
然而,RDF 只是一个概念。为了使用 RDF 和 SPARQL,我们仍然需要找到一个像样的 API 或实现,最好是在 Java 中。这使我们有了 2 个流行的选择,Sesame 和 Apache Jena。这两个 API 都被广泛接受,并且有多个供应商实现。更好的是,一些供应商为它们提供了实现。
本体论
在上面的例子中,很容易看出要进行有意义的查询,我们需要知道数据是什么样子的。因此,我们最终还是应该有某种数据模式。然而,这个模式应该更像元数据而不是数据定义。通常,我们不需要在插入数据之前预先定义模式。
为了解决这个问题,有两种策略。人们开始为 RDF 定义一个通用词汇表。因为大多数时候我们已经知道资源之间的关系,但不知道资源本身,词汇表应该足够好以帮助形成 SPARQL 查询。然而,由于描述世界的范围很广,任何词汇都不足以完全表达每一个领域。因此,有许多领域特定的词汇表。
第一种方法仅处理描述资源之间可能的关系,而第二种方法也尝试描述资源。例如,为了描述前面示例中的资源,我们可以定义一个名为智能手机的类和一个名为制造商的类。在此之后,我们可以指定制造商可以生产手机。
<http://example.org/Apple> <http://example.org/produce> <http://example.org/iPhone5>
<http://example.org/iPhone5> <http://example.org/price> '600 USD'@en
上面的那些三元组形成了一个本体。与词汇表相比,我们发现 Ontology 对数据模式的描述更具描述性,因为它可以告诉我们哪种关系适用于哪种资源。因此,我们不会浪费时间去研究iPhone 5生产苹果还是苹果生产iPhone 5。
Ontology加RDF是构建知识库的不错选择。虽然存储库可以是一个可以容纳任何三元组的 RDF 存储,但我们可以在单独的空间中构建另一个本体来对主存储库中的知识进行建模。
从我们的角度来看,最好使用本体来形成查询而不是数据验证,因为它有助于稍微解耦数据和模式。在实践中,插入不适合本体的数据是完全可以接受的,只要它们不矛盾即可。
例如,对于之前定义的本体,我们应该允许插入像这样的知识
<http://example.org/Apple> <http://example.org/produce> <http://example.org/iPhone5>
<http://example.org/iPhone5> <http://example.org/price> '600 USD'@en
但我们可以拒绝任何知识
<http://example.org/Apple> <http://example.org/produce> <http://example.org/iPhone5>
<http://example.org/iPhone5> <http://example.org/price> '600 USD'@en
通过这种方法,我们可以先导入数据,然后再对它们进行建模。
数据库模式
一个令人耳目一新的想法
将我们构建知识数据库的方法与关系数据库联系起来让我觉得在数据插入之前定义数据模式的要求是由实现驱动的。如果我们可以暂时忘记数据索引等实际问题,则可以先插入数据,然后再定义数据模式。
这也让我产生了另一种想法,即同一份数据是否可以有多个数据模式。这个想法起初可能听起来很尴尬,但如果我们看一下为世界建模的艰巨任务,它就会非常现实。例如,如果我们把奥巴马看作美国总统,我们可能会关心他什么时候开始任职以及他什么时候离任。但是如果我们把奥巴马当做其他美国公民,那么我们更关心的是出生日期、居住区、安全号码……这样,模式就作为我们检查和修改资源的一个视角。
所以,如果我能回到人们讨论数据库通用查询语言的时代,我会建议为 SQL 添加一些特性来丰富它,或者引入一种不太严格的新型查询语言:
- 允许在没有表定义的情况下插入。根据插入命令参数自动创建表定义。
- 使每个数据库的记录 ID 唯一,而不是每个表唯一。一条记录可以出现在许多具有相同 ID 的表中。插入命令需要指定哪个字段是记录的 id。
- 数据定义更通用,没有大小限制(varchar 和 int 而不是 varchar(255) 或 int(11))。
- 选择命令必须符合表定义。
- 可以在同一记录的两个表之间共享一个字段。
在结束本文之前,让我们尝试快速构建一个可以实现这些扩展功能的数据库。底层存储系统可以是任何无模式引擎,但为简单起见,我们可以使用 RDF 存储。
要求
- 将 Obama 插入带有姓名、年龄和性别的美国公民表。标识符字段是名称。
- Insert Obama into US president table with name, age and elected year.标识符字段是名称。
- 使用字段名称和年龄定义美国公民表。
- Define US president table with name, age and elected year.
- 从美国公民表中选择记录将仅显示姓名和年龄,因为表定义中未定义性别。
- 将美国总统表中的奥巴马记录更新为新年龄会影响美国公民表中的年龄,因为它是一个共享字段。
实现
步骤1
- SQL: 插入公民(姓名、性别、年龄)值('Barack Obama', 'Male', 53)
- 三元组:
- <巴拉克奥巴马> <类型> <公民>
- <巴拉克奥巴马> <名称> '巴拉克奥巴马'
- <巴拉克奥巴马> <性别> '男性'
- <奥巴马> <年龄> 53
第2步
- SQL: 插入 president(name, elected_year) value ('Barrack Obama', 'Male', 53)
- 三元组:
- <巴拉克奥巴马> <类型> <总统>
- <巴拉克奥巴马> <elected_year> 2009
步骤 3
- SQL: 创建表公民 ('name' varchar, 'age' int, primary key ('name') )
- 三元组:
- <公民> <领域> <名字>
- <公民> <领域> <年龄>
- <公民> <primary_key> <名字>
步骤4
- SQL: 创建表 president ('name' varchar, 'elected_year' int, primary key ('name') )
- 三元组:
- <总裁> <领域> <姓名>
- <president> <field> <elect_year>
- <总裁> <primary_key> <名称>
步骤 5
- SQL: 从公民中选择 *
- SPARQL: 选择 ?record ?field_name ?field_value 其中 {
- ?record <类型> <公民>
- <公民> <领域> ?field_name
- ?记录 ?字段名 ?字段值
- }
步骤 6
- SQL: 更新 president set age=54 where name='Barack Obama'
- 用 <Barack Obama> <age> 54 覆盖 <Barack Obama> <age> 53
结论
如果观众不熟悉 RDF 或 Ontology,我认为上面的一些想法有点难以消化。尽管如此,我还是希望它能引起更多关于弥合关系数据库和知识数据库之间差距的想法。