mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
ch 100%
This commit is contained in:
parent
e6f8f26892
commit
e3c38d5b44
227
ch2.md
227
ch2.md
@ -42,7 +42,7 @@
|
||||
|
||||
多年来,在数据存储和查询方面存在着许多相互竞争的方法。在20世纪70年代和80年代初,网络模型和分层模型曾是主要的选择,但关系模型随后占据了主导地位。对象数据库在20世纪80年代末和90年代初来了又去。XML数据库在二十一世纪初出现,但只有小众采用过。关系模型的每个竞争者都在其时代产生了大量的炒作,但从来没有持续【2】。
|
||||
|
||||
随着电脑越来越强大和互联,它们开始用于日益多样化的目的。关系数据库非常成功地被推广到业务数据处理的原始范围之外更为广泛的用例上。您今天在网上看到的大部分内容依旧是由关系数据库来提供支持,无论是在线发布,讨论,社交网络,电子商务,游戏,软件即服务生产力应用程序等等内容。
|
||||
随着电脑越来越强大和互联,它们开始用于日益多样化的目的。关系数据库非常成功地被推广到业务数据处理的原始范围之外更为广泛的用例上。你今天在网上看到的大部分内容依旧是由关系数据库来提供支持,无论是在线发布,讨论,社交网络,电子商务,游戏,软件即服务生产力应用程序等等内容。
|
||||
|
||||
### NoSQL的诞生
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
目前大多数应用程序开发都使用面向对象的编程语言来开发,这导致了对SQL数据模型的普遍批评:如果数据存储在关系表中,那么需要一个笨拙的转换层,处于应用程序代码中的对象和表,行,列的数据库模型之间。模型之间的不连贯有时被称为**阻抗不匹配(impedance mismatch)**[^i]。
|
||||
|
||||
[^i]: 一个从电子学借用的术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当您将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配会导致信号反射及其他问题。
|
||||
[^i]: 一个从电子学借用的术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当你将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配会导致信号反射及其他问题。
|
||||
|
||||
像ActiveRecord和Hibernate这样的**对象关系映射(object-relational mapping, ORM)**框架可以减少这个转换层所需的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。
|
||||
|
||||
@ -138,7 +138,7 @@ JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部
|
||||
* 本地化支持——当网站翻译成其他语言时,标准化的列表可以被本地化,使得地区和行业可以使用用户的语言来显示
|
||||
* 更好的搜索——例如,搜索华盛顿州的慈善家就会匹配这份简介,因为地区列表可以编码记录西雅图在华盛顿这一事实(从“Greater Seattle Area”这个字符串中看不出来)
|
||||
|
||||
存储ID还是文本字符串,这是个**复制(duplication)**问题。当使用ID时,对人类有意义的信息(比如单词:Philanthropy)只存储在一处,所有引用它的地方使用ID(ID只在数据库中有意义)。当直接存储文本时,对人类有意义的信息会复制在每处使用记录中。
|
||||
存储ID还是文本字符串,这是个**副本(duplication)**问题。当使用ID时,对人类有意义的信息(比如单词:Philanthropy)只存储在一处,所有引用它的地方使用ID(ID只在数据库中有意义)。当直接存储文本时,对人类有意义的信息会复制在每处使用记录中。
|
||||
|
||||
使用ID的好处是,ID对人类没有任何意义,因而永远不需要改变:ID可以保持不变,即使它标识的信息发生变化。任何对人类有意义的东西都可能需要在将来某个时候改变——如果这些信息被复制,所有的冗余副本都需要更新。这会导致写入开销,也存在不一致的风险(一些副本被更新了,还有些副本没有被更新)。去除此类重复是数据库**规范化(normalization)**的关键思想。[^ii]
|
||||
|
||||
@ -231,7 +231,7 @@ CODASYL中的查询是通过利用遍历记录列和跟随访问路径表在数
|
||||
|
||||
文档数据库对连接的糟糕支持也许或也许不是一个问题,这取决于应用程序。例如,分析应用程可能永远不需要多对多的关系,如果它使用文档数据库来记录何事发生于何时【19】。
|
||||
|
||||
但是,如果您的应用程序确实使用多对多关系,那么文档模型就没有那么吸引人了。通过反规范化可以减少对连接的需求,但是应用程序代码需要做额外的工作来保持数据的一致性。通过向数据库发出多个请求,可以在应用程序代码中模拟连接,但是这也将复杂性转移到应用程序中,并且通常比由数据库内的专用代码执行的连接慢。在这种情况下,使用文档模型会导致更复杂的应用程序代码和更差的性能【15】。
|
||||
但是,如果你的应用程序确实使用多对多关系,那么文档模型就没有那么吸引人了。通过反规范化可以减少对连接的需求,但是应用程序代码需要做额外的工作来保持数据的一致性。通过向数据库发出多个请求,可以在应用程序代码中模拟连接,但是这也将复杂性转移到应用程序中,并且通常比由数据库内的专用代码执行的连接慢。在这种情况下,使用文档模型会导致更复杂的应用程序代码和更差的性能【15】。
|
||||
|
||||
很难说在一般情况下哪个数据模型让应用程序代码更简单;它取决于数据项之间存在的关系种类。对于高度相联的数据,选用文档模型是糟糕的,选用关系模型是可接受的,而选用图形模型(参见“[图数据模型](#图数据模型)”)是最自然的。
|
||||
|
||||
@ -267,7 +267,7 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
|
||||
读时模式更具优势,当由于某种原因(例如,数据是异构的)集合中的项目并不都具有相同的结构时。例如,因为:
|
||||
|
||||
* 存在许多不同类型的对象,将每种类型的对象放在自己的表中是不现实的。
|
||||
* 数据的结构由外部系统决定。您无法控制外部系统且它随时可能变化。
|
||||
* 数据的结构由外部系统决定。你无法控制外部系统且它随时可能变化。
|
||||
|
||||
在这样的情况下,模式的坏处远大于它的帮助,无模式文档可能是一个更加自然的数据模型。但是,要是所有记录都具有相同的结构,那么模式是记录并强制这种结构的有效机制。第四章将更详细地讨论模式和模式演化。
|
||||
|
||||
@ -329,7 +329,7 @@ SELECT * FROM animals WHERE family ='Sharks';
|
||||
|
||||
命令式语言告诉计算机以特定顺序执行某些操作。可以想象一下,逐行地遍历代码,评估条件,更新变量,并决定是否再循环一遍。
|
||||
|
||||
在声明式查询语言(如SQL或关系代数)中,您只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合) - 但不是如何实现这一目标。数据库系统的查询优化器决定使用哪些索引和哪些连接方法,以及以何种顺序执行查询的各个部分。
|
||||
在声明式查询语言(如SQL或关系代数)中,你只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合) - 但不是如何实现这一目标。数据库系统的查询优化器决定使用哪些索引和哪些连接方法,以及以何种顺序执行查询的各个部分。
|
||||
|
||||
声明式查询语言是迷人的,因为它通常比命令式API更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在无需对查询做任何更改的情况下进行性能提升。
|
||||
|
||||
@ -387,7 +387,7 @@ li.selected > p {
|
||||
|
||||
这里的XPath表达式`li[@class='selected']/p`相当于上例中的CSS选择器`li.selected> p`。CSS和XSL的共同之处在于,它们都是用于指定文档样式的声明式语言。
|
||||
|
||||
想象一下,必须使用命令式方法,情况会是如何。在Javascript中,使用**文档对象模型(DOM)**API,其结果可能如下所示:
|
||||
想象一下,必须使用命令式方法的情况会是如何。在Javascript中,使用**文档对象模型(DOM)**API,其结果可能如下所示:
|
||||
|
||||
```js
|
||||
var liElements = document.getElementsByTagName("li");
|
||||
@ -408,7 +408,7 @@ for (var i = 0; i < liElements.length; i++) {
|
||||
|
||||
* 如果选定的类被移除(例如,因为用户点击了不同的页面),即使代码重新运行,蓝色背景也不会被移除 - 因此该项目将保持突出显示,直到整个页面被重新加载。使用CSS,浏览器会自动检测`li.selected> p`规则何时不再适用,并在选定的类被移除后立即移除蓝色背景。
|
||||
|
||||
* 如果您想要利用新的API(例如`document.getElementsBy ClassName(“selected”`)甚至`document.evaluate()`)来提高性能,则必须重写代码。另一方面,浏览器供应商可以在不破坏兼容性的情况下提高CSS和XPath的性能。
|
||||
* 如果你想要利用新的API(例如`document.getElementsBy ClassName(“selected”`)甚至`document.evaluate()`)来提高性能,则必须重写代码。另一方面,浏览器供应商可以在不破坏兼容性的情况下提高CSS和XPath的性能。
|
||||
|
||||
在Web浏览器中,使用声明式CSS样式比使用JavaScript命令式地操作样式要好得多。类似地,在数据库中,使用像SQL这样的声明式查询语言比使用命令式查询API要好得多[^vi]。
|
||||
|
||||
@ -435,7 +435,7 @@ WHERE family = 'Sharks'
|
||||
GROUP BY observation_month;
|
||||
```
|
||||
|
||||
`date_trunc('month',timestamp)`函数确定包含`timestamp`的日历月份,并返回代表该月份开始的另一个时间戳记。换句话说,它将时间戳舍入成最近的月份。
|
||||
`date_trunc('month',timestamp)`函数用于确定包含`timestamp`的日历月份,并返回代表该月份开始的另一个时间戳。换句话说,它将时间戳舍入成最近的月份。
|
||||
|
||||
这个查询首先过滤观察记录,以只显示鲨鱼家族的物种,然后根据它们发生的日历月份对观察记录果进行分组,最后将在该月的所有观察记录中看到的动物数目加起来。
|
||||
|
||||
@ -504,17 +504,17 @@ db.observations.aggregate([
|
||||
]);
|
||||
```
|
||||
|
||||
聚合管道语言与SQL的子集具有类似表现力,但是它使用基于JSON的语法而不是SQL的英语句子式语法; 这种差异也许是口味的问题。这个故事的寓意是NoSQL系统可能会发现自己意外地重新发明了SQL,尽管带着伪装。
|
||||
聚合管道语言与SQL的子集具有类似表现力,但是它使用基于JSON的语法而不是SQL的英语句子式语法; 这种差异也许是口味问题。这个故事的寓意是NoSQL系统可能会发现自己意外地重新发明了SQL,尽管带着伪装。
|
||||
|
||||
|
||||
|
||||
## 图数据模型
|
||||
|
||||
如我们之前所见,多对多关系是不同数据模型之间的重要区别特征。如果您的应用程序大多数是一对多关系(树状结构化数据),或者记录之间没有关系,则文档模型是适当的。
|
||||
如我们之前所见,多对多关系是不同数据模型之间具有区别性的重要特征。如果你的应用程序大多数的关系是一对多关系(树状结构化数据),或者大多数记录之间不存在关系,那么使用文档模型是合适的。
|
||||
|
||||
但是,如果多对多的关系在您的数据中很常见呢?关系模型可以处理多对多关系的简单情况,但是随着数据之间的连接变得更加复杂,开始将数据建模为图形变得更加自然。
|
||||
但是,要是多对多关系在你的数据中很常见呢?关系模型可以处理多对多关系的简单情况,但是随着数据之间的连接变得更加复杂,将数据建模为图形显得更加自然。
|
||||
|
||||
一个图由两种对象组成:**顶点(vertices)**(也称为**节点(nodes)** 或**实体(entities)**),和**边(edges)**( 也称为**关系(relationships)**或**弧 (arcs)** )。许多种数据可以被模拟为一个图形。典型的例子包括:
|
||||
一个图由两种对象组成:**顶点(vertices)**(也称为**节点(nodes)** 或**实体(entities)**),和**边(edges)**( 也称为**关系(relationships)**或**弧 (arcs)** )。多种数据可以被建模为一个图形。典型的例子包括:
|
||||
|
||||
***社交图谱***
|
||||
|
||||
@ -522,15 +522,15 @@ db.observations.aggregate([
|
||||
|
||||
***网络图谱***
|
||||
|
||||
顶点是网页,边缘表示到其他页面的HTML链接。
|
||||
顶点是网页,边缘表示指向其他页面的HTML链接。
|
||||
|
||||
***公路或铁路网络***
|
||||
|
||||
顶点是连接点,边线代表它们之间的道路或铁路线。
|
||||
顶点是交叉路口,边线代表它们之间的道路或铁路线。
|
||||
|
||||
众所周知的算法可以在这些图上进行操作:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank可以用在网络图上来确定网页的流行程度,从而其在搜索结果中的排名。
|
||||
可以将那些众所周知的算法运用到这些图上:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank可以用在网络图上来确定网页的流行程度,从而确定该网页在搜索结果中的排名。
|
||||
|
||||
在刚刚给出的例子中,图中的所有顶点代表了相同类型的东西(人,网页或交叉路口)。然而,图并不局限于这样的同类数据:图的同样强大的用途是提供一种在单个数据存储中存储完全不同类型的对象的一致方式。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示用户所做的人,地点,事件,签到和评论;边缘表示哪些人是彼此的朋友,哪个位置发生了检查,谁评论了哪个职位,谁参加了哪个事件,等等【35】。
|
||||
在刚刚给出的例子中,图中的所有顶点代表了相同类型的事物(人,网页或交叉路口)。不过,图并不局限于这样的同类数据:同样强大地是,图提供了一种一致的方式,用来在单个数据存储中存储完全不同类型的对象。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示人,地点,事件,签到和用户的评论;边缘表示哪些人是彼此的朋友,哪个签到发生在何处,谁评论了哪条消息,谁参与了哪个事件,等等【35】。
|
||||
|
||||
在本节中,我们将使用[图2-5](img/fig2-5.png)所示的示例。它可以从社交网络或系谱数据库中获得:它显示了两个人,来自爱达荷州的Lucy和来自法国Beaune的Alain。他们已婚,住在伦敦。
|
||||
|
||||
@ -538,26 +538,26 @@ db.observations.aggregate([
|
||||
|
||||
**图2-5 图数据结构示例(框代表顶点,箭头代表边)**
|
||||
|
||||
有几种不同但相关的方法来构建和查询图表中的数据。在本节中,我们将讨论属性图模型(由Neo4j,Titan和InfiniteGraph实现)和三元组存储(triple-store)模型(由Datomic,AllegroGraph等实现)。我们将看图的三种声明式查询语言:Cypher,SPARQL和Datalog。除此之外,还有像Gremlin 【36】这样的图形查询语言和像Pregel这样的图形处理框架(见[第10章](ch10.md))。
|
||||
有几种不同但相关的方法用来构建和查询图表中的数据。在本节中,我们将讨论属性图模型(由Neo4j,Titan和InfiniteGraph实现)和三元组存储(triple-store)模型(由Datomic,AllegroGraph等实现)。我们将查看图的三种声明式查询语言:Cypher,SPARQL和Datalog。除此之外,还有像Gremlin 【36】这样的图形查询语言和像Pregel这样的图形处理框架(见[第10章](ch10.md))。
|
||||
|
||||
### 属性图
|
||||
|
||||
在属性图模型中,每个**顶点(vertex)**包括:
|
||||
|
||||
* 唯一的标识符
|
||||
* **出向边集合(outgoing edges)**
|
||||
* **入向边集合(ingoing edges)**
|
||||
* 一组**出边(outgoing edges)**
|
||||
* 一组**入边(ingoing edges)**
|
||||
* 一组属性(键值对)
|
||||
|
||||
每条**边(edge)**包括:
|
||||
|
||||
* 唯一标识符
|
||||
* **边的起点/尾点(tail vertex)**
|
||||
* **边的终点/头点(head vertex)**
|
||||
* **边的起点/尾部顶点(tail vertex)**
|
||||
* **边的终点/头部顶点(head vertex)**
|
||||
* 描述两个顶点之间关系类型的标签
|
||||
* 一组属性(键值对)
|
||||
|
||||
可以将图存储看作两个关系表:一个存储顶点,另一个存储边,如[例2-2]()所示(该模式使用PostgreSQL json数据类型来存储每个顶点或边的属性)。头部和尾部顶点存储为每个边缘。如果您想要一组顶点的输入或输出边,您可以分别通过`head_vertex`或`tail_vertex`来查询边表。
|
||||
可以将图存储看作由两个关系表组成:一个存储顶点,另一个存储边,如[例2-2]()所示(该模式使用PostgreSQL json数据类型来存储每个顶点或每条边的属性)。头部和尾部顶点用来存储每条边;如果你想要一组顶点的输入或输出边,你可以分别通过`head_vertex`或`tail_vertex`来查询`edges`表。
|
||||
|
||||
**例2-2 使用关系模式来表示属性图**
|
||||
|
||||
@ -579,21 +579,21 @@ CREATE INDEX edges_tails ON edges (tail_vertex);
|
||||
CREATE INDEX edges_heads ON edges (head_vertex);
|
||||
```
|
||||
|
||||
这个模型的一些重要方面是:
|
||||
关于这个模型的一些重要方面是:
|
||||
|
||||
1. 任何顶点都可以有一个边连接到任何其他顶点。没有哪种事物可不可以关联的模式限制。
|
||||
2. 给定任何顶点,您可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动。(这就是为什么[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因。)
|
||||
3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个干净的数据模型。
|
||||
1. 任何顶点都可以有一条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。
|
||||
2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动。(这就是为什么[例2-2]()在`tail_vertex`和`head_vertex`列上都有索引的原因。)
|
||||
3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个清晰的数据模型。
|
||||
|
||||
这些特性为数据建模提供了很大的灵活性,如[图2-5](img/fig2-5.png)所示。图中显示了一些传统关系模式难以表达的东西,例如不同国家的不同地区结构(法国有省和州,美国有不同的州和州),国中国的怪事(先忽略主权国家和国家错综复杂的烂摊子),不同的数据粒度(露西现在的住所被指定为一个城市,而她的出生地点只是在一个州的级别)。
|
||||
这些特性为数据建模提供了很大的灵活性,如[图2-5](img/fig2-5.png)所示。图中显示了一些传统关系模式难以表达的事情,例如不同国家的不同地区结构(法国有省和州,美国有不同的州和州),国中国的怪事(先忽略主权国家和国家错综复杂的烂摊子),不同的数据粒度(Lucy现在的住所被指定为一个城市,而她的出生地点只是在一个州的级别)。
|
||||
|
||||
你可以想象延伸图还包括许多关于露西和阿兰,或其他人的其他事实。例如,您可以用它来表示食物过敏(通过为每个过敏原引入一个顶点,以及人与过敏原之间的边缘来指示过敏),并将过敏原与一组过敏原显示哪些食物含有哪些物质的顶点。然后,你可以写一个查询,找出每个人吃什么是安全的。图表对于可演化性是有利的:当您向应用程序添加功能时,可以轻松扩展图形以适应应用程序数据结构的变化。
|
||||
你可以想象延伸图还能包括许多关于Lucy和Alain,或其他人的其他更多的事实。例如,你可以用它来表示食物过敏(为每个过敏源增加一个顶点,并增加人与过敏源之间的一条边来指示一种过敏情况),并链接到过敏源,每个过敏源具有一组顶点用来显示哪些食物含有哪些物质。然后,你可以写一个查询,找出每个人吃什么是安全的。图表在可演化性是富有优势的:当向应用程序添加功能时,可以轻松扩展图以适应应用程序数据结构的变化。
|
||||
|
||||
### Cypher查询语言
|
||||
|
||||
Cypher是属性图的声明式查询语言,为Neo4j图形数据库发明【37】。(它是以电影“黑客帝国”中的角色命名的,与密码术中的密码无关【38】。)
|
||||
Cypher是属性图的声明式查询语言,为Neo4j图形数据库而发明【37】。(它是以电影“黑客帝国”中的一个角色开命名的,而与密码术中的密码无关【38】。)
|
||||
|
||||
[例2-3]()显示了将[图2-5](img/fig2-5.png)的左边部分插入图形数据库的Cypher查询。图的其余部分可以类似地添加,为了便于阅读而省略。每个顶点都有一个像USA或Idaho这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] ->(USA)`创建一个带有标记`WITHIN`的边,爱达荷州为尾节点,美国为头节点。
|
||||
[例2-3]()显示了将[图2-5](img/fig2-5.png)的左边部分插入图形数据库的Cypher查询。可以类似地添加图的其余部分,为了便于阅读而省略。每个顶点都有一个像`USA`或`Idaho`这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] ->(USA)`创建一条标记为`WITHIN`的边,`Idaho`为尾节点,`USA`为头节点。
|
||||
|
||||
**例2-3 将图2-5中的数据子集表示为Cypher查询**
|
||||
|
||||
@ -607,9 +607,11 @@ CREATE
|
||||
(Lucy) -[:BORN_IN]-> (Idaho)
|
||||
```
|
||||
|
||||
当[图2-5](img/fig2-5.png)的所有顶点和边被添加到数据库时,我们可以开始提出有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,在这里我们想要找到在美国有一个`BORN_IN`边缘的所有顶点,还有一个`LIVING_IN`边缘到欧洲的一个位置,并且返回每个这些顶点的名称属性。
|
||||
当[图2-5](img/fig2-5.png)的所有顶点和边被添加到数据库后,让我们提些有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,这里我们想要找到符合下面条件的所有顶点,并且返回这些顶点的`name`属性:该顶点拥有一条连到美国任一位置的`BORN_IN`边,和一条连到欧洲的任一位置的`LIVING_IN`边。
|
||||
|
||||
**例2-4 展示了如何在Cypher中表达这个查询。在MATCH子句中使用相同的箭头符号来查找图中的模式:**
|
||||
[例2-4]()展示了如何在Cypher中表达这个查询。在MATCH子句中使用相同的箭头符号来查找图中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配`BORN_IN`边的任意两个顶点。该边的尾节点被绑定了变量`person`,头节点则未被绑定。
|
||||
|
||||
**例2-4 查找所有从美国移民到欧洲的人的Cypher查询:**
|
||||
|
||||
```cypher
|
||||
MATCH
|
||||
@ -618,35 +620,38 @@ MATCH
|
||||
RETURN person.name
|
||||
```
|
||||
|
||||
查询可以被读取如下:
|
||||
查询按如下来解读:
|
||||
|
||||
> 找到满足以下两者的所有顶点(称之为person):
|
||||
> 1. 有一条到某个顶点`BORN_IN`类型的出边。从那个顶点开始可以沿着一系列`WITHIN`出边最终到达类型为`Location`,`name=United States`的顶点
|
||||
> 找到满足以下两个条件的所有顶点(称之为person顶点):
|
||||
> 1. `person`顶点拥有一条到某个顶点的`BORN_IN`出边。从那个顶点开始,沿着一系列`WITHIN`出边最终到达一个类型为`Location`,`name`属性为`United States`的顶点。
|
||||
>
|
||||
> 2. `person`顶点还拥有一条`LIVES_IN`出边。沿着这条边,可以通过一系列`WITHIN`出边最终到达一个类型为`Location`,`name`属性为`Europe`的顶点。
|
||||
>
|
||||
> 2. 也有一条到某个顶点`LIVES_IN`类型的出边。沿着这条边,可以通过一系列`WITHIN`出边最终到达类型为`Location`,`name=Europe`的顶点
|
||||
>
|
||||
> 对于这样的`Person`类型顶点,返回其`name`属性。
|
||||
> 对于这样的`Person`顶点,返回其`name`属性。
|
||||
|
||||
这条查询有几种可行的查询路径。这里给出的描述建议你首先扫描数据库中的所有人,检查每个人的出生地和居住地,然后只返回符合条件的人。
|
||||
执行这条查询可能会有几种可行的查询路径。这里给出的描述建议首先扫描数据库中的所有人,检查每个人的出生地和居住地,然后只返回符合条件的那些人。
|
||||
|
||||
等价地,也可以从两个位置顶点开始并向后查找。如果名称属性上有一个索引,则可以高效地找到代表美国和欧洲的两个顶点。然后,您可以继续查找所有在`WITHIN`边中的位置(美国和欧洲的所有位置(州,地区,城市等))。最后,您可以查找可以通过在某个位置顶点处传入的`BORN_IN`或`LIVES_IN`边找到的人员。
|
||||
等价地,也可以从两个`Location`顶点开始反向地查找。假如`name`属性上有索引,则可以高效地找到代表美国和欧洲的两个顶点。然后,沿着所有`WITHIN`入边,可以继续查找出所有在美国和欧洲的位置(州,地区,城市等)。最后,查找出那些可以由`BORN_IN`或`LIVES_IN`入边到那些位置顶点的人。
|
||||
|
||||
对于声明式查询语言来说,典型的情况是,在编写查询语句时,您不需要指定执行细节:查询优化程序会自动选择预测效率最高的策略,因此您可以继续编写其余的应用程序。
|
||||
通常对于声明式查询语言来说,在编写查询语句时,不需要指定执行细节:查询优化程序会自动选择预测效率最高的策略,因此你可以继续编写应用程序的其他部分。
|
||||
|
||||
### SQL中的图查询
|
||||
|
||||
[例2-2]()建议可以在关系数据库中表示图数据。但是,如果我们把图数据放入关系结构中,我们是否也可以使用SQL查询它?答案是肯定的,但有些困难。在关系数据库中,您通常会事先知道在查询中需要哪些连接。在图查询中,您可能需要在找到要查找的顶点之前,遍历可变数量的边。也就是说,连接的数量事先并不确定。
|
||||
[例2-2]()建议在关系数据库中表示图数据。但是,如果把图数据放入关系结构中,我们是否也可以使用SQL查询它?
|
||||
|
||||
在我们的例子中,这发生在Cypher查询中的`() -[:WITHIN*0..]-> ()`规则中。一个人的`LIVES_IN`边缘可以指向任何类型的位置:街道,城市,地区,地区,国家等。城市可以在一个地区,在一个州内的一个地区,在一个国家内的一个州等等。`LIVES_IN`边可以直接指向你正在查找的位置,或者可以在位置层次结构中删除几个级别。
|
||||
在Cypher中,`WITHIN * 0`表示这个事实非常简洁:意思是“沿着一个`WITHIN`边,零次或多次”。它就像正则表达式中的`*`运算符。
|
||||
由于SQL:1999,查询中可变长度遍历路径的思想可以使用称为递归公用表表达式(`WITH RECURSIVE`语法)的东西来表示。[例2-5]()显示了同样的查询 - 查找使用这种技术(PostgreSQL,IBM DB2,Oracle和SQL Server支持)中从美国移民到欧洲的人的姓名。但是,与Cypher相比,语法非常笨拙。
|
||||
答案是肯定的,但有些困难。在关系数据库中,你通常会事先知道在查询中需要哪些连接。在图查询中,你可能需要在找到待查找的顶点之前,遍历可变数量的边。也就是说,连接的数量事先并不确定。
|
||||
|
||||
**例2-5 与示例2-4相同的查询,使用递归CTE表达式在SQL中表示**
|
||||
在我们的例子中,这发生在Cypher查询中的`() -[:WITHIN*0..]-> ()`规则中。一个人的`LIVES_IN`边可以指向任何类型的位置:街道,城市,地区,地区,国家等。城市可以在一个地区,在一个州内的一个地区,在一个国家内的一个州等等。`LIVES_IN`边可以直接指向正在查找的位置,或者一个在位置层次结构中隔了数层的位置。
|
||||
|
||||
在Cypher中,用`WITHIN * 0`非常简洁地表述了上述事实:“沿着`WITHIN`边,零次或多次”。它很像正则表达式中的`*`运算符。
|
||||
|
||||
自SQL:1999,查询可变长度遍历路径的思想可以使用称为**递归公用表表达式**(`WITH RECURSIVE`语法)的东西来表示。[例2-5]()显示了同样的查询 - 查找从美国移民到欧洲的人的姓名 - 在SQL使用这种技术(PostgreSQL,IBM DB2,Oracle和SQL Server均支持)来表述。但是,与Cypher相比,其语法非常笨拙。
|
||||
|
||||
**例2-5 与示例2-4同样的查询,在SQL中使用递归公用表表达式表示**
|
||||
|
||||
```sql
|
||||
WITH RECURSIVE
|
||||
-- in_usa 包含所有的美国境内的地点ID
|
||||
-- in_usa 包含所有的美国境内的位置ID
|
||||
in_usa(vertex_id) AS (
|
||||
SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'United States'
|
||||
UNION
|
||||
@ -654,7 +659,7 @@ WITH RECURSIVE
|
||||
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
|
||||
WHERE edges.label = 'within'
|
||||
),
|
||||
-- in_europe 包含所有的欧洲境内的地点ID
|
||||
-- in_europe 包含所有的欧洲境内的位置ID
|
||||
in_europe(vertex_id) AS (
|
||||
SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'Europe'
|
||||
UNION
|
||||
@ -680,24 +685,25 @@ WITH RECURSIVE
|
||||
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;
|
||||
```
|
||||
|
||||
* 集合`in_usa`以(`Location name=United States`)作为种子,沿着边表,从种子集作为起点,将所有具有`with_in`边的终点加入种子集,不断递归直到边表内的所有条目都被访问完毕。
|
||||
* 同理建立点集`in_europe`,获取欧洲的地点列表。
|
||||
* 对于`in_usa`集合中的每个顶点,按照传入的`born_in`边缘来查找出生在美国某个地方的人。
|
||||
* 同样,对于`in_europe`集合中的每个顶点,请按照传入的`lives_in`边来查找居住在欧洲的人。
|
||||
* 最后,把在美国出生的人与在欧洲居住的人相交,获取他们的名称。
|
||||
* 首先,查找`name`属性为`United States`的顶点,将其作为`in_use`顶点的集合的第一个元素。
|
||||
* 从`in_use`集合的顶点出发,沿着所有的`with_in`入边,将其尾顶点加入同一集合,不断递归直到所有`with_in`入边都被访问完毕。
|
||||
* 同理,从`name`属性为`Europe`的顶点出发,建立`in_europe`顶点的集合。
|
||||
* 对于`in_usa`集合中的每个顶点,根据`born_in`入边来查找出生在美国某个地方的人。
|
||||
* 同样,对于`in_europe`集合中的每个顶点,根据`lives_in`入边来查找居住在欧洲的人。
|
||||
* 最后,把在美国出生的人的集合与在欧洲居住的人的集合相交。
|
||||
|
||||
同一个查询,可以用一个查询语言写成4行,而另一个查询需要29行,这说明了不同的数据模型是为不同的应用场景设计的。选择适合应用程序的数据模型非常重要。
|
||||
同一个查询,用某一个查询语言可以写成4行,而用另一个查询语言需要29行,这恰恰说明了不同的数据模型是为不同的应用场景而设计的。选择适合应用程序的数据模型非常重要。
|
||||
|
||||
### 三元组存储和SPARQL
|
||||
|
||||
三元组存储模式大体上与属性图模型相同,用不同的词来描述相同的想法。不过值得讨论的是,因为三元组存储有很多现成的工具和语言,这些工具和语言对于构建应用程序的工具箱可能是有价值的补充。
|
||||
三元组存储模式大体上与属性图模型相同,用不同的词来描述相同的想法。不过仍然值得讨论,因为三元组存储有很多现成的工具和语言,这些工具和语言对于构建应用程序的工具箱可能是宝贵的补充。
|
||||
|
||||
在三元组存储中,所有信息都以非常简单的三部分表示形式存储(主体,谓词,客体)。例如,在三重(吉姆,喜欢,香蕉),吉姆是主语,喜欢是谓语(动词),香蕉是对象。
|
||||
在三元组存储中,所有信息都以非常简单的三部分表示形式存储(**主语**,**谓语**,**宾语**)。例如,三元组**(吉姆, 喜欢 ,香蕉)**中,**吉姆**是主语,**喜欢**是谓语(动词),**香蕉**是对象。
|
||||
|
||||
三元组的主体相当于图中的一个顶点。而客体是两件事情之一:
|
||||
三元组的主语相当于图中的一个顶点。而宾语是下面两者之一:
|
||||
|
||||
1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓词和对象相当于主题顶点上的属性的键和值。例如,`(lucy, age, 33)`就像属性`{“age”:33}`的顶点lucy。
|
||||
2. 图中的另一个顶点。在这种情况下,谓词是图中的边,主体是尾部顶点,而对象是顶点。例如,在`(lucy, marriedTo, alain)`主语和宾语`lucy`和`alain`都是顶点,并且谓词`marriedTo`是连接他们的边的标签。
|
||||
1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如,`(lucy, age, 33)`就像属性`{“age”:33}`的顶点lucy。
|
||||
2. 图中的另一个顶点。在这种情况下,谓语是图中的一条边,主语是其尾部顶点,而宾语是其头部顶点。例如,在`(lucy, marriedTo, alain)`中主语和宾语`lucy`和`alain`都是顶点,并且谓语`marriedTo`是连接他们的边的标签。
|
||||
|
||||
[例2-6]()显示了与[例2-3]()相同的数据,以称为Turtle的格式(Notation3(N3)【39】)的一个子集形式写成三元组。
|
||||
|
||||
@ -721,11 +727,11 @@ _:namerica :name "North America"
|
||||
_:namerica :type :"continent"
|
||||
```
|
||||
|
||||
在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确三元组之间的相互引用。当谓词表示边时,该对象是一个顶点,如:`_:idaho :within _:usa.`。当谓词是一个属性时,该对象是一个字符串,如:`_:usa :name "United States"`
|
||||
在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确哪些三元组引用了同一顶点。当谓语表示边时,该宾语是一个顶点,如`_:idaho :within _:usa.`。当谓语是一个属性时,该宾语是一个字符串,如`_:usa :name "United States"`
|
||||
|
||||
一遍又一遍地重复相同的主题是相当重复的,但幸运的是,您可以使用分号来说明关于同一主题的多个事情。这使得Turtle格式相当不错,可读性强:参见[例2-7]()。
|
||||
一遍又一遍地重复相同的主语看起来相当重复,但幸运的是,可以使用分号来说明关于同一主语的多个事情。这使得Turtle格式相当不错,可读性强:参见[例2-7]()。
|
||||
|
||||
**例2-7 在示例2-6 `@prefix:<urn:example:>`中写入数据的更简洁的方法。**
|
||||
**例2-7 一种相对例2-6写入数据的更为简洁的方法。**
|
||||
|
||||
```
|
||||
@prefix : <urn:example:>.
|
||||
@ -737,21 +743,21 @@ _:namerica a :Location; :name "North America"; :type "continent".
|
||||
|
||||
#### 语义网络
|
||||
|
||||
如果您阅读更多关于三元组存储的信息,您可能会被卷入关于语义网的文章中。三元组存储数据模型完全独立于语义网络,例如,Datomic 【40】是三元组存储[^vii],并不声称与它有任何关系。但是,由于在很多人眼中这两者紧密相连,我们应该简要地讨论一下。
|
||||
如果你阅读更多关于三元组存储的信息,你可能会被卷入关于语义网络的文章漩涡中。三元组存储数据模型完全独立于语义网络,例如,Datomic【40】是三元组存储[^vii],并没有声称与它有任何关系。但是,由于在很多人眼中这两者紧密相连,我们应该简要地讨论一下。
|
||||
|
||||
[^vii]: 从技术上讲,Datomic使用的是五元组而不是三元组,两个额外的字段是用于版本控制的元数据
|
||||
|
||||
语义网从本质上讲是一个简单而合理的想法:网站已经将信息发布为文字和图片供人类阅读,为什么不把它们作为机器可读的数据发布给计算机呢?资源描述框架(RDF)【41】的目的是作为不同网站以一致的格式发布数据的一种机制,允许来自不同网站的数据自动合并成一个数据网络 - 一种互联网范围内的“一切的数据库“。
|
||||
从本质上讲语义网是一个简单且合理的想法:网站已经将信息发布为文字和图片供人类阅读,为什么不将信息作为机器可读的数据也发布给计算机呢?**资源描述框架**(RDF)【41】的目的是作为不同网站以一致的格式发布数据的一种机制,允许来自不同网站的数据自动合并成**一个数据网络** - 一种互联网范围内的“关于一切的数据库“。
|
||||
|
||||
不幸的是,这个语义网在二十一世纪初被过度使用,但到目前为止还没有显示出在实践中有任何实现的迹象,这使得许多人愤世嫉俗。它也遭受了令人眼花缭乱的缩略词,过于复杂的标准提议和自大。
|
||||
不幸的是,这个语义网在二十一世纪初被过度使用,但到目前为止没有任何迹象表明已在实践中实现,这使得许多人呲之以鼻。它还遭受了过多的令人眼花缭乱的缩略词,过于复杂的标准提议和狂妄自大的苦果。
|
||||
|
||||
但是,如果你仔细观察这些失败,那么语义Web项目中也有很多好的工作。即使您没有兴趣在语义网上发布RDF数据,三元组也可以成为应用程序的良好内部数据模型。
|
||||
然而,如果仔细观察这些失败,语义Web项目还是拥有很多优秀的工作成果。即使你没有兴趣在语义网上发布RDF数据,三元组也可以成为应用程序的良好内部数据模型。
|
||||
|
||||
#### RDF数据模型
|
||||
|
||||
[例2-7]()中使用的Turtle语言是RDF数据的可读格式。有时候,RDF也是以XML格式编写的,它可以更详细地完成同样的事情,参见[例2-8]()。Turtle N3是最好的,因为它更容易阅读,像Apache Jena 【42】这样的工具可以根据需要在不同的RDF格式之间自动转换。
|
||||
[例2-7]()中使用的Turtle语言是一种用于RDF数据的人可读格式。有时候,RDF也可以以XML格式编写,不过完成同样的事情会相对啰嗦,参见[例2-8]()。Turtle/N3是更可取的,因为它更容易阅读,像Apache Jena 【42】这样的工具可以根据需要在不同的RDF格式之间进行自动转换。
|
||||
|
||||
**例2-8 例2-7的数据,用RDF/XML语法表示**
|
||||
**例2-8 用RDF/XML语法表示例2-7的数据**
|
||||
|
||||
```xml
|
||||
<rdf:RDF xmlns="urn:example:"
|
||||
@ -779,14 +785,15 @@ _:namerica a :Location; :name "North America"; :type "continent".
|
||||
</rdf:RDF>
|
||||
```
|
||||
|
||||
RDF有一些奇怪之处,因为它是为了在互联网上交换数据而设计的。三元组的主题,谓词和对象通常是URI。例如,谓词可能是一个URI,如 `<http://my-company.com/namespace#within>`或`<http://my-company.com/namespace#lives_in>`,而不仅仅是WITHIN或LIVES_IN。这个设计背后的原因是你应该能够把你的数据和其他人的数据结合起来,如果他们给这个单词或者lives_in附加不同的含义,你不会得到冲突,因为它们的谓词实际上是`<http://other.org/foo#within>`和`<http://other.org/foo#lives_in>`。
|
||||
RDF有一些奇怪之处,因为它是为了在互联网上交换数据而设计的。三元组的主语,谓语和宾语通常是URI。例如,谓语可能是一个URI,如 `<http://my-company.com/namespace#within>`或`<http://my-company.com/namespace#lives_in>`,而不仅仅是`WITHIN`或`LIVES_IN`。这个设计背后的原因为了让你能够把你的数据和其他人的数据结合起来,如果他们赋予单词`within`或者`lives_in`不同的含义,两者也不会冲突,因为它们的谓语实际上是`<http://other.org/foo#within>`和`<http://other.org/foo#lives_in>`。
|
||||
|
||||
从RDF的角度来看,URL `<http://my-company.com/namespace>` 不一定需要能解析成什么东西,它只是一个命名空间。为避免与`http://URL`混淆,本节中的示例使用不可解析的URI,如`urn:example:within`。幸运的是,您只需在文件顶部指定一个前缀,然后忘记它。
|
||||
从RDF的角度来看,URL `<http://my-company.com/namespace>` 不一定需要能解析成什么东西,它只是一个命名空间。为避免与`http://URL`混淆,本节中的示例使用不可解析的URI,如`urn:example:within`。幸运的是,你只需在文件顶部指定一个前缀,然后就不用再管了。
|
||||
|
||||
### SPARQL查询语言
|
||||
|
||||
SPARQL是RDF数据模型三元组存储的查询语言【43】。(它是SPARQL协议和RDF查询语言的缩写,发音为“sparkle”。)它早于Cypher,并且由于Cypher的模式匹配是从SPARQL中借用的,所以它们看起来非常相似【37】。
|
||||
与从前从美国转移到欧洲的人相同的查询——在SPARQL中比在Cypher中更加简洁(参见[例2-9]())。
|
||||
**SPARQL**是一种用于三元组存储的面向RDF数据模型的查询语言,【43】。(它是SPARQL协议和RDF查询语言的缩写,发音为“sparkle”。)SPARQL早于Cypher,并且由于Cypher的模式匹配借鉴于SPARQL,这使得它们看起来非常相似【37】。
|
||||
|
||||
与之前相同的查询 - 查找从美国转移到欧洲的人 - 使用SPARQL比使用Cypher甚至更为简洁(参见[例2-9]())。
|
||||
|
||||
**例2-9 与示例2-4相同的查询,用SPARQL表示**
|
||||
|
||||
@ -799,46 +806,46 @@ SELECT ?personName WHERE {
|
||||
}
|
||||
```
|
||||
|
||||
结构非常相似。以下两个表达式是等价的(变量以SPARQL中的问号开头):
|
||||
结构非常相似。以下两个表达式是等价的(SPARQL中的变量以问号开头):
|
||||
|
||||
```
|
||||
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher
|
||||
?person :bornIn / :within* ?location. # SPARQL
|
||||
```
|
||||
|
||||
由于RDF不区分属性和边,而只是使用两个属性,所以可以使用相同的语法来匹配属性。在下面的表达式中,变量usa被绑定到任何具有值为字符串`"United States"`的`name`属性的顶点:
|
||||
因为RDF不区分属性和边,而只是将它们作为谓语,所以可以使用相同的语法来匹配属性。在下面的表达式中,变量`usa`被绑定到任意具有值为字符串`"United States"`的`name`属性的顶点:
|
||||
|
||||
```
|
||||
(usa {name:'United States'}) # Cypher
|
||||
?usa :name "United States". # SPARQL
|
||||
```
|
||||
|
||||
SPARQL是一种很好的查询语言——即使语义网从来没有出现,它也可以成为应用程序内部使用的强大工具。
|
||||
SPARQL是一种很好的查询语言——哪怕语义网从未实现,它仍然可以成为一种应用程序内部使用的强大工具。
|
||||
|
||||
> #### 图形数据库与网络模型相比较
|
||||
>
|
||||
> 在“[文档数据库是否在重蹈覆辙?](#文档数据库是否在重蹈覆辙?)”中,我们讨论了CODASYL和关系模型如何竞争解决IMS中的多对多关系问题。乍一看,CODASYL的网络模型看起来与图模型相似。CODASYL是否是图形数据库的第二个变种?
|
||||
> 在“[文档数据库是否在重蹈覆辙?](#文档数据库是否在重蹈覆辙?)”中,我们讨论了CODASYL和关系模型如何竞相解决IMS中的多对多关系问题。乍一看,CODASYL的网络模型看起来与图模型相似。CODASYL是否是图形数据库的第二个变种?
|
||||
>
|
||||
> 不,他们在几个重要方面有所不同:
|
||||
>
|
||||
> * 在CODASYL中,数据库有一个模式,指定哪种记录类型可以嵌套在其他记录类型中。在图形数据库中,不存在这样的限制:任何顶点都可以具有到其他任何顶点的边。这为应用程序适应不断变化的需求提供了更大的灵活性。
|
||||
> * 在CODASYL中,数据库有一个模式,用于指定哪种记录类型可以嵌套在其他记录类型中。在图形数据库中,不存在这样的限制:任何顶点都可以具有到其他任何顶点的边。这为应用程序适应不断变化的需求提供了更大的灵活性。
|
||||
> * 在CODASYL中,达到特定记录的唯一方法是遍历其中的一个访问路径。在图形数据库中,可以通过其唯一ID直接引用任何顶点,也可以使用索引来查找具有特定值的顶点。
|
||||
> * 在CODASYL,记录的孩子们的有序集合,所以数据库的人不得不维持排序(其中有用于存储布局的后果),并且插入新记录到数据库的应用程序不得不担心的新记录的位置在这些集合中。在图形数据库中,顶点和边不是有序的(您只能在查询时对结果进行排序)。
|
||||
> * 在CODASYL中,所有查询都是必要的,难以编写,并且很容易被架构中的变化破坏。在图形数据库中,如果需要,可以在命令式代码中编写遍历,但大多数图形数据库也支持高级声明式查询语言,如Cypher或SPARQL。
|
||||
> * 在CODASYL,记录的后续是一个有序集合,所以数据库的人不得不维持排序(这会影响存储布局),并且插入新记录到数据库的应用程序不得不担心的新记录在这些集合中的位置。在图形数据库中,顶点和边不是有序的(只能在查询时对结果进行排序)。
|
||||
> * 在CODASYL中,所有查询都是命令式的,难以编写,并且很容易因架构中的变化而受到破坏。在图形数据库中,如果需要,可以在命令式代码中编写遍历,但大多数图形数据库也支持高级声明式查询语言,如Cypher或SPARQL。
|
||||
>
|
||||
>
|
||||
|
||||
### 基础:Datalog
|
||||
|
||||
Datalog是比SPARQL或Cypher更古老的语言,在20世纪80年代被学者广泛研究【44,45,46】。它在软件工程师中是不太知名的,但是它是重要的,因为它为以后的查询语言提供了基础。
|
||||
**Datalog**是比SPARQL或Cypher更古老的语言,在20世纪80年代被学者广泛研究【44,45,46】。它在软件工程师中不太知名,但是它是重要的,因为它为以后的查询语言提供了基础。
|
||||
|
||||
在实践中,Datalog在一些数据系统中被使用:例如,它是Datomic 【40】的查询语言,而Cascalog 【47】是用于查询Hadoop中的大数据集的Datalog实现[^viii]。
|
||||
在实践中,Datalog被用于少数的数据系统中:例如,它是Datomic 【40】的查询语言,Cascalog 【47】是一种用于查询Hadoop大数据集的Datalog实现[^viii]。
|
||||
|
||||
[^viii]: Datomic和Cascalog使用Datalog的Clojure S表达式语法。在下面的例子中使用了一个更容易阅读的Prolog语法,没有任何功能差异。
|
||||
[^viii]: Datomic和Cascalog使用Datalog的Clojure S表达式语法。在下面的例子中使用了一个更容易阅读的Prolog语法,但两者没有任何功能差异。
|
||||
|
||||
Datalog的数据模型类似于三元组模式,但有一点泛化。我们把它写成谓词(主语,宾语),而不是写三元语(主语,宾语,宾语)。[例2-10]()显示了如何在Datalog中写入我们的例子中的数据。
|
||||
Datalog的数据模型类似于三元组模式,但进行了一点泛化。把三元组写成**谓语**(**主语,宾语**),而不是写三元语(**主语,宾语,宾语**)。[例2-10]()显示了如何用Datalog写入我们的例子中的数据。
|
||||
|
||||
**例2-10 图2-5中的数据子集,表示为Datalog事实**
|
||||
**例2-10 用Datalog来表示图2-5中的数据子集**
|
||||
|
||||
```prolog
|
||||
name(namerica, 'North America').
|
||||
@ -856,9 +863,9 @@ name(lucy, 'Lucy').
|
||||
born_in(lucy, idaho).
|
||||
```
|
||||
|
||||
现在我们已经定义了数据,我们可以像之前一样编写相同的查询,如**例2-11**所示。它看起来有点不同于Cypher或SPARQL的等价物,但是不要让你失望。Datalog是Prolog的一个子集,如果你已经学过计算机科学,你可能已经见过。
|
||||
既然已经定义了数据,我们可以像之前一样编写相同的查询,如[例2-11]()所示。它看起来有点不同于Cypher或SPARQL的等价物,但是请不要放弃它。Datalog是Prolog的一个子集,如果你学过计算机科学,你可能已经见过。
|
||||
|
||||
**例2-11 与示例2-4相同的查询,在Datalog中表示**
|
||||
**例2-11 与示例2-4相同的查询,用Datalog表示**
|
||||
|
||||
```
|
||||
within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */
|
||||
@ -876,52 +883,52 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
|
||||
|
||||
```
|
||||
|
||||
Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只需要一小步。我们定义告诉数据库有关新谓词的规则:在这里,我们定义了两个新的谓词,在`_recursive`和`migrated`内。这些谓词不是存储在数据库中的三元组,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者自发地调用自己一样。像这样,复杂的查询可以一次构建一小块。
|
||||
Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只进行一小步。我们定义**规则**,以将新谓语告诉数据库:在这里,我们定义了两个新的谓语,`_recursive`和`migrated`。这些谓语不是存储在数据库中的三元组中,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者递归地调用自己一样。像这样,复杂的查询可以一次构建其中的一小块。
|
||||
|
||||
在规则中,以大写字母开头的单词是变量,谓词匹配如Cypher和SPARQL。例如,`name(Location, Name)`与可变绑定`Location = namerica`和`Name ='North America'`的三重名称`(namerica, 'North America')`匹配。
|
||||
在规则中,以大写字母开头的单词是变量,谓语则用Cypher和SPARQL的方式一样来匹配。例如,`name(Location, Name)`通过变量绑定`Location = namerica`和`Name ='North America'`可以匹配三元组`name(namerica, 'North America')`。
|
||||
|
||||
如果系统可以在` -` 操作符的右侧找到与所有谓词相匹配的规则,则适用该规则。当规则适用时,就好像将`-`的左边添加到数据库(用变量替换它们的值)。
|
||||
要是系统可以在`:-` 操作符的右侧找到与所有谓语的一个匹配,就运用该规则。当规则运用时,就好像通过`:-`的左侧将其添加到数据库(将变量替换成它们匹配的值)。
|
||||
|
||||
因此,应用规则的一种可能的方式是:
|
||||
因此,一种可能的应用规则的方式是:
|
||||
|
||||
1. 名称`(namerica, '北美')`存在于数据库中,故规则1适用。它生成`within_recursive(namerica, '北美')`。
|
||||
2. 数据库中存在`(usa, namerica)`,并在之前的步骤中生成`(namerica, 'North America')`,所以适用规则2。它会产生`within_recursive(美国, “北美”)`。
|
||||
3. 在`(爱达荷州, 美国)`存在于数据库和上一步生成`within_recursive(美国, “北美”)`,所以规则2适用。它产生`within_recursive(爱达荷州, "北美")`。
|
||||
1. 数据库存在`name(namerica, 'North America')`,故运用规则1。它生成`within_recursive(namerica, 'North America')`。
|
||||
2. 数据库存在`within(usa, namerica)`,在上一步骤中生成`within_recursive(namerica, 'North America')`,故运用规则2。它会产生`within_recursive(usa, 'North America')`。
|
||||
3. 数据库存在`within(idaho, usa)`,在上一步生成`within_recursive(usa, 'North America')`,故运用规则2。它产生`within_recursive(idaho, 'North America')`。
|
||||
|
||||
通过重复应用规则1和2,`within_recursive`谓词可以告诉我们在我们的数据库中包含的北美(或任何其他位置名称)的所有位置。这个过程如[图2-6](img/fig2-6.png)所示。
|
||||
通过重复应用规则1和2,`within_recursive`谓语可以告诉我们在数据库中包含北美(或任何其他位置名称)的所有位置。这个过程如[图2-6](img/fig2-6.png)所示。
|
||||
|
||||
![](img/fig2-6.png)
|
||||
|
||||
**图2-6 使用示例2-11中的Datalog规则确定爱达荷州在北美。**
|
||||
**图2-6 使用示例2-11中的Datalog规则来确定爱达荷州在北美。**
|
||||
|
||||
现在规则3可以找到出生在某个地方BornIn的人,并住在某个地方LivingIn。通过查询`BornIn ='United States'`和`LivingIn ='Europe'`,并将此人作为变量`Who`,我们要求Datalog系统找出变量`Who`可以出现哪些值。因此,最后我们得到了与早先的Cypher和SPARQL查询相同的答案。
|
||||
现在规则3可以找到出生在某个地方`BornIn`的人,并住在某个地方`LivingIn`。通过查询`BornIn ='United States'`和`LivingIn ='Europe'`,并将此人作为变量`Who`,让Datalog系统找出变量`Who`会出现哪些值。因此,最后得到了与早先的Cypher和SPARQL查询相同的答案。
|
||||
|
||||
Datalog方法需要对本章讨论的其他查询语言采取不同的思维方式,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。简单的一次性查询不太方便,但是如果数据很复杂,它可以更好地处理。
|
||||
相对于本章讨论的其他查询语言,我们需要采取不同的思维方式来思考Datalog方法,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。虽然对于简单的一次性查询,显得不太方便,但是它可以更好地处理数据很复杂的情况。
|
||||
|
||||
|
||||
|
||||
## 本章小结
|
||||
|
||||
数据模型是一个巨大的课题,在本章中,我们快速浏览了各种不同的模型。我们没有足够的空间来详细介绍每个模型的细节,但是希望这个概述足以激起您的兴趣,以更多地了解最适合您的应用需求的模型。
|
||||
数据模型是一个巨大的课题,在本章中,我们快速浏览了各种不同的模型。我们没有足够的空间来详细介绍每个模型的细节,但是希望这个概述足以激起你的兴趣,以更多地了解最适合你的应用需求的模型。
|
||||
|
||||
在历史上,数据开始被表示为一棵大树(层次数据模型),但是这不利于表示多对多的关系,所以发明了关系模型来解决这个问题。最近,开发人员发现一些应用程序也不适合在关系模型中使用。新的非关系型“NoSQL”数据存储在两个主要方向上有分歧:
|
||||
在历史上,数据最开始被表示为一棵大树(层次数据模型),但是这不利于表示多对多的关系,所以发明了关系模型来解决这个问题。最近,开发人员发现一些应用程序也不适合采用关系模型。新的非关系型“NoSQL”数据存储在两个主要方向上存在分歧:
|
||||
|
||||
1. 文档数据库的应用场景是:数据通常是自我包含的,而且文档之间的关系非常罕见。
|
||||
2. 图形数据库用于相反的场景: 任何东西都可能和任何东西相关联。
|
||||
1. **文档数据库**的应用场景是:数据通常是自我包含的,而且文档之间的关系非常稀少。
|
||||
2. **图形数据库**用于相反的场景:任意事物都可能与任何事物相关联。
|
||||
|
||||
所有这三种模型(文档,关系和图形)今天都被广泛使用,并且在各自的领域都是很好用的。一个模型可以用另一个模型来模拟 —— 例如,图数据可以在关系数据库中表示 —— 但结果往往是尴尬的。这就是为什么我们有着用于不同目的的不同系统,而不是一个单一的万能解决方案。
|
||||
这三种模型(文档,关系和图形)在今天都被广泛使用,并且在各自的领域都发挥很好。一个模型可以用另一个模型来模拟 — 例如,图数据可以在关系数据库中表示 — 但结果往往是糟糕的。这就是为什么我们有着针对不同目的的不同系统,而不是一个单一的万能解决方案。
|
||||
|
||||
文档数据库和图数据库有一个共同点,那就是它们通常不会为存储的数据强制实施一个模式,这可以使应用程序更容易适应不断变化的需求。但是应用程序很可能仍假定数据具有一定的结构:这只是模式是明确的(强制写入)还是隐含的(在读取时处理)的问题。
|
||||
文档数据库和图数据库有一个共同点,那就是它们通常不会为存储的数据强制一个模式,这可以使应用程序更容易适应不断变化的需求。但是应用程序很可能仍会假定数据具有一定的结构;这只是模式是明确的(写入时强制)还是隐含的(读取时处理)的问题。
|
||||
|
||||
每个数据模型都带有自己的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB的聚合管道,Cypher,SPARQL和Datalog。我们也谈到了CSS和 XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。
|
||||
每个数据模型都具有各自的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB的聚合管道,Cypher,SPARQL和Datalog。我们也谈到了CSS和XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。
|
||||
|
||||
虽然我们已经覆盖了很多地方,但仍然有许多数据模型没有提到。举几个简单的例子:
|
||||
虽然我们已经覆盖了很多层面,但仍然有许多数据模型没有提到。举几个简单的例子:
|
||||
|
||||
* 研究人员使用基因组数据通常需要执行序列相似性搜索,这意味着需要一个很长的字符串(代表一个DNA分子),并将其与一个类似但不完全相同的大型字符串数据库进行匹配。这里所描述的数据库都不能处理这种用法,这就是为什么研究人员编写了像GenBank这样的专门的基因组数据库软件的原因【48】。
|
||||
* 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析,像大型强子对撞机(LHC)这样的项目现在可以工作在数百亿兆字节的范围内!在这样的规模下,需要定制解决方案来阻止硬件成本从失控中解脱出来【49】。
|
||||
* 全文搜索可以说是一种经常与数据库一起使用的数据模型。信息检索是一个大的专业课题,在本书中我们不会详细介绍,但是我们将在第三章和第三章中介绍搜索索引。
|
||||
* 使用基因组数据的研究人员通常需要执行**序列相似性搜索**,这意味着需要一个很长的字符串(代表一个DNA分子),并在一个拥有类似但不完全相同的字符串的大型数据库中寻找匹配。这里所描述的数据库都不能处理这种用法,这就是为什么研究人员编写了像GenBank这样的专门的基因组数据库软件的原因【48】。
|
||||
* 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析,像大型强子对撞机(LHC)这样的项目现在可以工作在数百亿兆字节的范围内!在这样的规模下,需要定制解决方案来阻住硬件成本的失控【49】。
|
||||
* **全文搜索**可以说是一种经常与数据库一起使用的数据模型。信息检索是一个很大的专业课题,我们不会在本书中详细介绍,但是我们将在第三章和第三章中介绍搜索索引。
|
||||
|
||||
在[下一章](ch3.md)中,我们将讨论在实现本章描述的数据模型时会遇到的一些权衡。
|
||||
让我们暂时将其放在一边。在[下一章](ch3.md)中,我们将讨论在**实现**本章描述的数据模型时会遇到的一些权衡。
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user