如果你对图形数据库以及它们与关系型数据库管理系统的比较感到好奇,那么这篇适合初学者的指南就是为你准备的。
在这篇文章中,你将通过处理一个小型电影数据集来发现图的力量。它是基于Neo4j沙盒上的内置数据集和指南。
你想直接进入并体验一下吗?那就来吧!你会在这里找到关于如何启动和运行的说明。
本文将介绍的内容
图形数据库的受欢迎程度和采用程度都在不断提高。随着来自许多不同来源的数据量越来越大,能够理解这些数据并看到它们之间的联系是至关重要的。
如果你想了解更多关于图谱数据库帮助解决的各种问题,以及你如何发现一个好的应用,这里有一篇介绍性的博文。
你们中有些人可能听说过图形数据库(GDB),有些人可能没有听说过。在这篇文章中,我们将介绍它们到底是什么,以及它们与更传统的关系型数据库管理系统(RDBMS)的比较,后者在过去40多年里一直是软件应用的中坚力量。
受Neo4j使用的小型电影数据集的启发,作为图查询的指导性介绍,我们将从侧面看图数据库和关系型数据库中的数据模型或查询的实例和等值情况。
在这篇文章中,我们将。
- 介绍图形数据库,简要介绍现有的两种模型
- 从概念上看关系型数据库和图型数据库之间的区别
- 看一下电影数据集,从GDB和RDBMS的角度比较和对比数据模型
- 比较和对比一些基于Cypher(用于GDB)或SQL的查询。
- 讨论一下电影中出现的更有趣的查询,并指出到底发生了什么。
如果你想在阅读这篇文章之前(或在阅读过程中)玩一玩演练电影数据集的例子,我们非常欢迎你这样做。你可以在这里找到更多信息。
什么是图形数据库?
首先,在我们深入了解什么是图形数据库之前,让我们来定义这个术语。图形数据库是一种 "不仅仅是SQL"(NoSQL)的数据存储。它们被设计用来在图结构中存储和检索数据。
使用的存储机制可以因数据库而异。一些GDB可能使用更传统的数据库结构,如基于表的,然后在上面有一个图API层。
另一些则是 "原生 "的GDB--从存储、管理到查询的整个数据库结构都保持着数据的图结构。目前的许多图形数据库通过将实体之间的关系作为第一类公民来处理。
图形数据库的不同类型
GDB大致有两种类型,资源描述框架(RDF)/三重存储/语义图数据库,以及属性图数据库。
RDF GDB使用三元组的概念,它是由三个元素组成的语句:主体-预测-对象。
主语将是图中的一个资源或节点,宾语将是另一个节点或字面价值,而谓语代表主语和宾语之间的关系。节点或关系上没有内部结构,一切都由一个独特的标识符(URI形式)来识别。
这种结构背后的动机是交换和发布数据。要想了解更多关于这种结构的信息,我想请你参考Jesus Barrasa在这个领域的工作。
一个属性GDB的重点是存储接近逻辑模型的数据的概念。这又将基于数据本身所寻求的问题,并专注于使该表示法在存储和查询方面尽可能地高效。
与基于RDF的图不同的是,在节点和关系上有内部的结构,从而使数据以及相关的元数据有丰富的表现。
下面两张图提供了在属性图数据库和RDF图中表示的样本数据的并列比较--这两张图都代表了汤姆-汉克斯,在电影《阿波罗13号》中扮演吉姆-洛弗尔这个角色。
阿波罗13号中汤姆-汉克斯的RDF例子
阿波罗13号中汤姆-汉克斯的属性图示例
属性图数据库剖析
在本文的其余部分,我们将重点讨论本地属性图数据库,特别是Neo4j。让我们来看看主要的组成部分。
属性图数据库的主要组成部分如下。
- 节点:在图论中也被称为顶点,是构建图的主要数据元素。
- 关系:在图论中也被称为边--两个节点之间的联系。它将有方向 和类型。一个没有关系的节点是允许的,一个没有两个节点的关系是不允许的
节点和关系
- 标签。定义了一个节点的类别,一个节点可以有一个以上的类别。
- 属性。丰富一个节点或关系,不需要空值
标签、类型和属性
图形数据库与关系型数据库
关系型数据库回顾
很多开发者都熟悉传统的关系型数据库,在这种数据库中,数据被存储在一个明确定义的模式中的表中。
表中的每一行都是一个离散的数据实体。行中的一个元素通常被用来定义其唯一性:主键。它可以是一个唯一的ID,也可能是像一个人的社会安全号码这样的东西。
然后,我们通过一个叫做规范化的过程来减少数据的重复性。在规范化过程中,我们要把引用,比如一个人的地址,移到另一个表中。因此,我们从代表实体的行中得到一个引用到代表该人的地址的行中。
例如,如果有人改变了他们的地址,你不希望到处都是这个人的地址的多个版本,并且必须努力记住这个人的地址存在的所有不同实例。规范化可以确保你有一个版本的数据,所以你可以在一个地方进行更新。
然后,当我们查询时,我们要重新构建这个规范化的数据。我们做了一个所谓的JOIN操作。
在我们的主要实体行中,我们有一个主键,用于识别实体的ID,比方说人。我们还有一个所谓的外键,代表我们地址表中的一条记录。我们通过这两个表的主键和外键来连接这两个表,并使用它来查询地址表中的地址。这被称为JOIN,这些JOIN是在查询时和读取时完成的。
当我们在关系型数据库中进行连接时,这是一个集合比较操作,我们要看我们的两个数据集在哪里重叠(在这个例子中,这两个集合是人表和地址表)。在高层次上,这就是传统关系数据库的工作方式。
在关系型数据库中找到的表以及它们如何相互映射的例子,是一个保险数据库的例子
财产图数据库中的保险数据库的等效例子
本地图数据库是如何工作的。连接和无索引的相邻关系
让我们快速浏览一下本地图数据库以及它是如何工作的。
我们谈到了关系型数据库中的离散实体是表内的一行。在本地图数据库中,该行相当于一个节点。它仍然是一个离散的实体,所以我们仍然有这个规范化的元素。
一个节点将是一个实体。如果我们有人的节点,我们会有一个节点代表一个人。而且我们会有某种程度的唯一性,比如说社会安全号码。
然而,关键的区别是,当我们将这个人的节点连接到另一个离散的实体--例如,一个地址--我们在这两点之间创建一个物理连接(又称关系)。
这个地址会有一个指针,表示连接到这个节点的关系的外向部分是什么?然后,我们有另一个指针,用于关系的入站部分,指向另一个节点。
因此,实际上,我们正在收集一组指针,这是这两个实体之间物理连接的表现。这就是最大的区别。
在关系型数据库中,你会用读取时的连接来重组数据,这意味着在查询时,它会去尝试并找出事情是如何映射在一起的。
在图数据库中,由于我们已经知道这两个元素是相连的,我们不需要在查询时查找映射。我们所做的就是遵循存储的与其他节点的关系。
这就是我们所说的无索引相邻性。这个无索引邻接的概念是理解本地图数据库与其他数据库系统相比的性能优化的关键。
无索引邻接意味着在本地图的遍历过程中,按照这些连接我的图中的节点的指针(关系),操作的性能不取决于图的整体大小。它取决于连接到你正在遍历的节点的关系数量。
当我们谈论JOIN是一个集合操作(相交)时,我们在关系数据库中使用索引来查看这两个集合重叠的地方。这意味着,随着表的增大,JOIN操作的性能开始减慢。
用大O的术语来说,这就像是使用索引的对数增长--类似于O(log n),而且还随着查询中JOIN的数量呈指数级增长。
另一方面,在图中遍历关系更多的是基于我们实际遍历的节点中的关系数量的线性增长,而不是图的整体大小。
这就是图数据库所做的基本查询时间优化,它给了我们无索引的邻接。从性能的角度来看,这确实是我们在考虑原生图数据库时要考虑的最重要的事情。
电影图的简单介绍
我们已经谈了很多关于图和关系数据库之间的理论差异。现在让我们开始看看一些侧面的比较。
电影图由一个数据集组成,包括演员、导演、制片人、编剧、评论家和电影,以及他们之间的联系信息。
这个数据集可以在Neo4j浏览器中找到,并且可以通过使用:PLAY movies 命令来轻松触发。作为提醒,这里有一篇博客,告诉你如何开始。
电影数据集由以下部分组成
- 133个人物节点/实体
- 38个电影节点/实体
- 上述实体之间有253个关系/联系,描述了诸如以下的联系。
- 执导一部电影的人
- 在电影中表演的人和扮演的角色
- 撰写电影的人
- 制作电影的人
- 对电影进行评论的人,以及给出的分数和摘要
- 关注另一个人的人
虽然这是一个相对较小的数据集,但它全面地描述了图表的力量。
比较数据模型
首先,让我们看一下我们各自数据库的数据模型。就像所有的数据模型一样,它们是什么样子的最终将取决于你所问的问题的类型。因此,让我们假设我们要问以下类型的问题。
- 一个人演过哪些电影?
- 一个人与哪些电影有联系?
- 一个人曾经合作过的所有合作演员是谁?
基于这些,以下是相关的潜在数据模型。
电影图的实体关系数据模型
电影图的属性图数据模型
你马上就会发现一些东西--那些ID已经消失了!因为我们一旦知道那里有连接,就会把数据连接在一起,所以我们不再需要它们,或者那些映射表,让我们知道不同的数据行是如何连接在一起的。
对比查询
现在让我们来比较一下一些查询。从:PLAY movies ,让我们看看Cypher查询的一些侧面比较,以及同等的SQL查询是什么样子。
我听到你问,什么是Cypher?Cypher是一种图查询语言,用于查询Neo4j图数据库。也有一个OpenCypher 版本,被其他一些供应商使用。
当我们通过查询时,应该开始变得更清楚,一个图数据库,伴随着一种查询语言来帮助探索关系,真正开始进入它自己。让我们开始寻找汤姆-汉克斯!
如何找到汤姆-汉克斯
MATCH (p:Person {name: "Tom Hanks"})
RETURN p
密码器
SELECT * FROM person
WHERE person.name = "Tom Hanks"
SQL
如何找到汤姆-汉克斯的电影
MATCH (:Person {name: “Tom Hanks”})-->(m:Movie)
RETURN m.title
SELECT movie.title FROM movie
INNER JOIN movie_person ON movie.movie_id = person_movie.movie_id
INNER JOIN person ON person_movie.person_id = person.person_id
WHERE person.name = "Tom Hanks"
如何查找汤姆-汉克斯导演的电影
MATCH (:Person {name: "Tom Hanks"})-[:DIRECTED]->(m:Movie)
RETURN m.title
SELECT movie.title FROM movie
INNER JOIN person_movie ON movie.movie_id = person_movie.movie_id
INNER JOIN person ON person_movie.person_id = person.person_id
INNER JOIN involvement ON person_movie.involve_id = involvement.involve_id
WHERE person.name = "Tom Hanks" AND involvement.title = "Director"
如何查找汤姆-汉克斯的合作演员
MATCH (:Person {name: "Tom Hanks"})-->(:Movie)<-[:ACTED_IN]-(coActor:Person)
RETURN coActor.name
WITH tom_movies AS (
SELECT movie.movie_id FROM movie
INNER JOIN person_movie ON movie.movie_id = person_movie.movie_id
INNER JOIN person ON person_movie.person_id = person.person_id
WHERE person.name = "Tom Hanks")
SELECT person.name FROM person
INNER JOIN person_movie ON tom_movies = person_movie.movie_id
INNER JOIN person ON person_movie.person_id = person.person_id
INNER JOIN involvement ON person_movie.involve_id = involvement.involve_id
WHERE involvement.title = "Actor"
更多使用Cypher的查询
希望你能了解到Cypher和SQL查询之间的区别。也许你也很想了解更多关于它们的信息!我们会在博文中进一步提供一些参考。
现在,让我们来看看其他一些Cypher查询,你可以在:PLAY movies 图例中找到,并解释一下是怎么回事。
如果没有典型的培根数字问题,任何电影图都是不完整的,我们的电影图也不例外!
到目前为止,我们所看的例子每次都是遍历一个关系。我们可以很容易地利用这些 "写时连接 "来遍历许多关系,以回答有趣的问题。
所以,回到凯文-培根这个数字。下面的查询将从凯文-培根这个人的节点开始,然后从这个开始点往外走4跳,带回所有连接的电影和人。
MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
RETURN DISTINCT hollywood
我们可以通过在查询模式的关系部分使用*1..4 的语法来做到这一点。
*表示一切1..4表示范围--1表示从1跳开始,4表示最多4跳的距离
我们可以在这个电影数据集上做的另一个图形化的事情是两个节点之间的最短路径。
在这个例子中,让我们找出凯文-培根和梅格-瑞恩之间的最短路径。你会发现我们在关系模式上又使用了* 语法--表明一切。
对你来说可能比较陌生的是p= 。你已经看到我们如何使用节点的引用(例如,在我们当前的查询中,bacon 或meg ),我们也可以对关系做同样的处理。
我们也可以为整个路径(即所有涉及的节点和关系)提供引用。我们使用的语法是refName = ,在这个例子中是p= 。
我们还使用Cypher函数shortestPath() - 这是一个简单的最短路径函数,将返回两个指定节点之间的第一条最短路径。请注意,可能还有另一条同样短的路径,但这个简单的函数将只返回遇到的第一条。
对于那些对其他与路径有关的函数感兴趣的人,可以查看APOC和GDS中的函数。
MATCH p=shortestPath(
(bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
RETURN p
给大家一个警告:你可能会看到[*] ,并想在没有shortestPath() 函数或1..4 范围约束的情况下运行你的图形。但这很可能导致一些意想不到的结果。
在我们关于凯文-培根和梅格-瑞恩的例子中,尽管在这个非常小的数据集中只有253个关系,但节点和关系之间的所有可能的路径组合很容易在培根和瑞恩之间产生数百万条不同的路径。
当在你的关系中使用* ,作为查询的一部分时,要非常谨慎地使用!这个问题不会出现在最短路径上,因为当遇到比当前确定的最短路径更长的潜在路径时,它会被立即放弃。
一个简单的建议查询
这里有两个查询,真正显示了图数据库的力量,我们可以很容易地利用我们数据中的连接来做一些建议。
在我们的第一个查询中,我们正在为汤姆-汉克斯寻找新的、尚未合作过的合作演员。该查询通过以下方式实现。
- 首先,找到所有已经和他合作过的合作演员
- 然后,找到所有合作者的合作者(称为合作演员)。
- 接下来,我们要排除那些已经和汤姆合作过的合作演员,同时确保合作演员不是汤姆本人。
- 最后,我们返回建议的合作演员的名字,我们将按照与他们合作过的合作演员的数量来排序--与该合作演员合作过的合作演员越多,推荐就越好。
MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
(coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors)
WHERE NOT (tom)-[:ACTED_IN]->()<-[:ACTED_IN]-(cocoActors)
AND tom <> cocoActors
RETURN cocoActors.name AS Recommended, count(*) AS Strength
ORDER BY Strength DESC
很好,所以我们已经找到了一些潜在的合作演员。在接下来的查询中,我们想推荐汤姆-克鲁斯作为汤姆-汉克斯的潜在新合作演员。但是,谁来把这些汤姆介绍给对方呢?我们又回到了电影图中。
在这个查询中,我们。
- 找到汤姆-汉克斯的合作演员,然后找到这些合作演员中哪些人也曾与汤姆-克鲁斯合作过。
- 然后,我们将返回合作演员以及他们与汤姆-汉克斯和汤姆-克鲁斯一起演过的电影。
MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
(coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cruise:Person {name:"Tom Cruise"})
RETURN tom, m, coActors, m2, cruise
最后一句话
我们已经结束了对电影数据库例子的研究。希望那些有关系型数据库背景的人对关系型数据库和图型数据库的异同有了更好的认识,并对Cypher查询语言有所了解。
如果你热衷于学习更多关于Neo4j的建模和查询的知识,请查看免费的图学院。
技术专家、数据怪人、问题解决者。开发者倡导者@Neo4j
如果你读到这里,请发推特给作者,向他们表示你的关心。鸣谢
FreeCodeCamp的开源课程已经帮助超过40,000人获得了开发者的工作。开始吧