一般情况下,我们使用数据库查找事物间的联系的时候,只需要短程关系的查询(两层以内的关联)。当需要进行更长程的,更广范围的关系查询时,就需要图数据库的功能。
而随着社交、电商、金融、零售、物联网等行业的快速发展,现实世界的事物之间织起了一张巨大复杂的关系网,传统数据库面对这样复杂关系往往束手无策。因此,图数据库应运而生。
图数据库(Graph database)指的是以图数据结构的形式来存储和查询数据的数据库。
从 http://db-engines.com/en/ranking 可以发现,Neo4j 是目前用的最多的图数据库,世界数据库排行榜上排名21位。
Neo4J属于原生图数据库,其使用的存储后端专门为图结构数据的存储和管理进行定制和优化的,在图上互相关联的节点在数据库中的物理地址也指向彼此,因此更能发挥出图结构形式数据的优势。
知识图谱中,知识的组织形式采用的就是图结构,所以非常适合用neo4j进行存储。
图数据库的优势在于:
- 性能上,对长程关系的查询速度快
- 擅于发现隐藏的关系,例如通过判断图上两点之间有没有走的通的路径,就可以发现事物间的关联
数据存储形式
neo4j的数据存储形式 主要是 节点(node)和 边(edge) 来组织数据。node可以代表知识图谱中的实体,edge可以用来代表实体间的关系,关系可以有方向,两端对应开始节点和结束节点。
另外,可以在node上加一个或多个标签(Node Label)表示实体的分类,以及一个键值对集合来表示该实体除了关系属性之外的一些额外属性。关系也可以附带额外的属性。
查询语言cypher
neo4j采用自己设计的查询语言cypher,其特点和sql有很多相似的地方。match、where、return是最常用到的关键词:
match: 相当于 sql中的select,用来说明查询匹配的数据模式(或者说图模式)
where: 用来限制node或者关系中部分属性的属性值,从而返回我们想要的数据
return: 返回节点或者关系
安装neo4j
这里我们使用docker安装neo4j,安装命令行如下:
docker run -d --name=Neo4j\
--publish=7474:7474 --publish=7687:7687 \
--volume=$HOME/neo4j/data:/data --volume=$HOME/neo4j/import:/import\
neo4j
根据配置参数,我们将容器内的7474端口挂载到外部宿主机的7474端口,并设置好文件夹的映射关系,注意/import文件夹下放的是将要导入数据库的csv文件。
接着在浏览器中打开 “ http://localhost:7474/ ”,就可以访问Neo4j管理界面了。
我们通过一个例子来说明如何运用neo4j数据库。
1. 导入数据
我们这里有两个csv文件如下图,左边的nodes_companies.csv是一部分公司节点,右边的edges_director_duration.csv是这些公司互相之间的服务关系。
把这两个文件放到neo4j根目录下的import文件夹内,使用LOAD...AS row语句读取,表示将csv文件按行读取,每行的变量名为row。再使用MERGE指令创建节点,将csv文件的第一列数据与第二列数据汇总为一个结点内的两条属性信息。
LOAD CSV WITH HEADERS FROM "file:///nodes_companies.csv" AS row
MERGE (c:Company {companyId:row.companyId, companyName:row.name})
这里提一下cypher中两个用于创建新的数据的两个关键词: create 和 merge
merge:在数据库中可以匹配到模式相同的数据就返回,没有则创建一条这样的数据(有则返回,没有则创建)
create: 无论如何,都会创建一条新的数据
上面再LOAD文件时使用merge可以避免导入完全重复的数据。
通过第二个csv文件的START_ID和END_ID字段为第一个csv文件的company之间建立联系,即不断遍历第二个文件的每一行,根据START_ID和END_ID使用where找到图中相应节点,并为它们添加相应的服务(INTERLOCK)关系,添加关系属性为weight。
LOAD CSV WITH HEADERS FROM "file:///edges_director_duration.csv" AS row
match (c1:company), (c2:company)
where row.START_ID = c1.id and row.END_ID = c2.id
create (c1)-[r:INTERLOCK{weight:row.years_served}]->(c2)
注意在cypher语句里,节点是用()括起来表示,关系则用 [] 括起来表示。
2.创建关系
这里我们尝试自己创建一条新的关系,比如在id = 281 和 id = 879 的两个节点间创建一条标签为“INTERLOCK”的关系。
先match和where锁定 id = 281 和 id = 879的两个公司节点,然后用create创建他们之间的关系,并添加特定关系属性信息(例如weight为10)。
cypher语句如下:
MATCH (c1:company),(c2:company)
WHERE c1.id = “281” AND c2.id = “879” CREATE (c1)-[r:INTERLOCK{weight:10}]->(c2) RETURN (c1)-[r]-(c2)
这条语句的意思是,匹配类别标签为company,id分别等于281和879的两个公司节点,设置变量名为c1和c2,在他们之间创建关系,关系变量名为r,这里 ()-[]-() 代表无向边,()-[]->() 代表有向边。
返回结果 (c1)-[r]-(c2) 匹配到的子图如下所示:
3.比较复杂的查询
下面这条语句会把所有公司中,指向其他公司的连接关系数超过75条的公司全部找出来。用空括号()代表任一节点,函数count() 计算关系的数量。
MATCH (c:company)-[r:INTERLOCK]->() WITH c, count(r) as relaNum WHERE relaNum>=75 RETURN c,relaNum
4.最短路径查询
neo4j还还内置实现了一套图搜索算法,并提供了相关函数接口,比如你想查询两个节点之间的最短路径,就可以用下面的查询语句:
- shortestPath():返回两节点间的最短路径
match (c1:company), (c2:company), p=shortestPath((c1)-[r:INTERLOCK*..10]->(c2))
where c1.id <> c2.id
return p,length(p) order by length(p) desc limit 1000
直接调用函数shortestPath,传入的参数为选定的关系,选取任意两个节点,<>表示id不相等,因为查找的两个点不能是同一个点,..10表示10度以内*的所有关系,返回降序排序的长度,限制在1000个防止内存溢出)
- allshortestpaths():返回两节点间所有的最短路径
MATCH (c1:company), (c2:company), p = allshortestpaths((c1)-[r:INTERLOCK*]-(c2))
WHERE c1.id <> c2.id
RETURN
extract(n in nodes(p)|n.name) as Nodes,
length(p) as pathLength,
reduce(s=0, e in relationships(p)| s + toInt(e.weight)) as pathDist
LIMIT 1000
语句中的pathLength是路径的边数(第一句return),pathDist是路径上所有带weight边的加权总和(第二句return)。