likes
comments
collection
share

Neo4j——一个强大的图谱数据库

作者站长头像
站长
· 阅读数 19
  • 开源 NoSQL 数据库,原生的图数据库,2003 年开始开发,使用 scala和java 语言,2007年开始发布;
  • 世界上最先进的图数据库之一,提供原生的图数据存储,检索和处理;
  • 采用属性图模型(Property graph model),极大的完善和丰富图数据模型;
  • 专属查询语言 Cypher,直观,高效;

官方文档:neo4j.com/docs/

Neo4j 有两个版本可供选择,社区版和企业版。 企业版包括社区版必须提供的所有功能,以及额外的企业需求,例如备份、集群和故障转移功能。

  • Community Edition 是 Neo4j 的全功能版本,适用于单实例部署。 它完全支持关键的 Neo4j 功能,例如符合 ACID 的事务、Cypher 和编程 API。 它非常适合学习 Neo4j、自己动手的项目和小型工作组中的应用程序。
  • Enterprise Edition 扩展了 Community Edition 的功能,以包括性能和可扩展性方面的关键特性,例如集群架构和在线备份功能。 其他安全功能包括基于角色的访问控制和 LDAP 支持,例如 Active Directory。 它是对规模和可用性有要求的生产系统的选择,例如商业解决方案和关键内部解决方案。

1、安装

基于docker

通过docker安装

  1. 拉取镜像

    docker pull neo4j
    
  2. 指定目录(以Users/xxx/data/neo4j为例)下建立四个基本的文件夹

    data——数据存放的文件夹

    logs——运行的日志文件夹

    conf——数据库配置文件夹(在配置文件neo4j.conf中配置包括开放远程连接、设置默认激活的数据库)

    import——为了大批量导入csv来构建数据库,需要导入的节点文件nodes.csv和关系文件rel.csv需要放到这个文件夹下)

    启动Neo4j:

    docker run -d --name myneo4j \  //-d表示容器后台运行 --name指定容器名字
    	-p 7474:7474 -p 7687:7687 \  //映射容器的端口号到宿主机的端口号
    	-v /Users/yingtao/neo4j/data:/data \  //把容器内的数据目录挂载到宿主机的对应目录下
    	-v /Users/yingtao/neo4j/logs:/logs \  //挂载日志目录
    	-v /Users/yingtao/neo4j/conf:/var/lib/neo4j/conf   //挂载配置目录
    	-v /Users/yingtao/neo4j/import:/var/lib/neo4j/import \  //挂载数据导入目录
    	--env NEO4J_AUTH=neo4j/mypassword \  //设定数据库的名字的访问密码
    	neo4j //指定使用的镜像
    

    运行代码模版:

    docker run -d --name myneo4j -p 7474:7474 -p 7687:7687 -v /Users/yingtao/neo4j/data:/data -v /Users/yingtao/neo4j/logs:/logs -v /Users/yingtao/neo4j/conf:/var/lib/neo4j/conf   -v /Users/yingtao/neo4j/import:/var/lib/neo4j/import --env NEO4J_AUTH=neo4j/mypassword neo4j 
    

基于desktop

下载地址:neo4j.com/download/

说明文档:neo4j.com/docs/deskto…

2、生态

官方介绍:neo4j.com/developer/g…

Neo4j Browser和Bloom是两个与桌面捆绑在一起的图形应用程序。浏览器用作查询工具,用于针对Neo4j图形数据运行Cypher查询并查看结果。Bloom 用于使用搜索输入可视化图形数据。除此之外,还可以从桌面内的图形应用库中获得其他图形应用。图形应用可以与桌面中当前运行的 DBMS 一起使用。

neo4j browser

neo4j browser 有点类似于 mysql 里面的 workbench 或者 mongo 里面的 RoboMongo,说白了就是一个数据库的客户端,但是 neo4j browser 做得非常友好,甚至看上去,用起来就像是一个成熟的产品一样。

Neo4j——一个强大的图谱数据库

neo4j bloom

这个产品却是实打实的站在用户层面去考虑,一下子就把 neo4j 的可用性,宜用性,实用性提高了好几个档次。

演示视频:传送门

3、语法

Cypher 是 Neo4j 数据库的查询语言,就如同 SQL 之于其他关系型数据库一样。Neo4j 作为一种图数据库,其数据均以节点、关系来存储。所以 Cypher 应该能够有某种语法来描述节点和关系,并能表征他们之间的关系。

节点语法

() # 匿名的节点
(matrix) # 使用一个变量 matrix 与这个节点关联
(:Movie) # 类型为 Movie 的节点
(matrix:Movie)
(matrix:Movie {title: "The Matrix"}) # 指含有特定属性的某类节点
(matrix:Movie {title: "The Matrix", released: 1997})

关系语法

--> # 非直接相连的关系
-[role]-> # 使用变量关联此关系
-[:ACTED_IN]-> # 类型为 ACTED_IN 的关系
-[role:ACTED_IN]->
-[role:ACTED_IN {roles: ["Neo"]}]-> # 含有特定属性的关系

模式语法

将节点和关系组合起来,得出一个模式,而后使用此模式进行匹配:

(keanu:Person:Actor {name:  "Keanu Reeves"} )
-[role:ACTED_IN     {roles: ["Neo"] } ]->
(matrix:Movie       {title: "The Matrix"} )

模式变量

模式由多个节点和关系组成,通常较长,可以将其保存为一个变量。在一条 cypher 中的其他位置,便可以使用此模式。

acted_in = (:Person)-[:ACTED_IN]->(:Movie)

子句

如同 SQL 中的 SELECTWHERE 等子句,在 cypher 中也有这类子句,用来进行查找、过滤、排序等操作。

创建

使用 CREATE 关键字能够创建节点、关系、模式,如下面语句,创建了一个类型为 Movie 的节点,且含有两个属性:

CREATE (:Movie { title:"The Matrix",released:1997 })

还可以同时使用多个 CREATE 创建更为复杂的模式:

CREATE (a:Person { name:"Tom Hanks",
  born:1956 })-[r:ACTED_IN { roles: ["Forrest"]}]->(m:Movie { title:"Forrest Gump",released:1994 })
CREATE (d:Person { name:"Robert Zemeckis", born:1951 })-[:DIRECTED]->(m)
RETURN a,d,r,m

Neo4j——一个强大的图谱数据库

为现有的节点添加关系:

MATCH (p:Person { name:"Tom Hanks" })
CREATE (m:Movie { title:"Cloud Atlas",released:2012 })
CREATE (p)-[r:ACTED_IN { roles: ['Zachry']}]->(m)
RETURN p,r,m

Neo4j——一个强大的图谱数据库

匹配

MATCH (m:Movie)
RETURN m


MATCH (p:Person { name:"Keanu Reeves" })
RETURN p


MATCH (p:Person { name:"Tom Hanks" })-[r:ACTED_IN]->(m:Movie)
RETURN m.title, r.roles

添加 label

match (n {id:desired-id})
set n :newLabel
return n

双向关系

原文链接:Modelling Data in Neo4j: Bidirectional Relationships

有些关系自然是双向的, 一个典型的例子就是好友关系,GraphAware 和 Neo Technology 是合作伙伴公司。 由于这是一种相互关系,我们可以分别将其建模为双向或无向关系。

Neo4j——一个强大的图谱数据库

但是neo4j并不直接提供这种关系,如果把这种关系实现为下面的这种方式,那么就多了一条没有必要的关系。

Neo4j——一个强大的图谱数据库

Neo4j API 允许开发人员在查询图形时完全忽略关系方向,如果他们愿意的话。 例如,在 Neo4j 自己的查询语言 Cypher 中,查找 Neo Technology 所有合作伙伴公司的查询的关键部分看起来像:

MATCH (neo)-[:PARTNER]-(partner)
//等同于
MATCH (neo)-[:PARTNER]->(partner) and MATCH (neo)<-[:PARTNER]-(partner)

因此,对伙伴关系建模的正确(或至少是最有效的)方法是使用具有任意方向的单个 PARTNER 关系。

Merge

有时候希望给某个节点添加属性,但又不能保证其存在于库中,此时可以使用 MERGE

首先查找某个模式,如果存在便得出这个模式,不存在则创建。在 ON CREATE 中指定的操作,会在创建的时候进行。

MERGE (m:Movie { title:"Cloud Atlas" })
ON CREATE SET m.released = 2012
RETURN m

如果不存在 pm 之间的 ACTED_IN 关系,则创建,并在创建时添加属性。

MATCH (m:Movie { title:"Cloud Atlas" })
MATCH (p:Person { name:"Tom Hanks" })
MERGE (p)-[r:ACTED_IN]->(m)
ON CREATE SET r.roles =['Zachry']
RETURN p,r,m

Neo4j——一个强大的图谱数据库

case表达式

通用条件表达式可以使用 CASE 结构来表达。 Cypher 中存在两种 CASE 变体:简单形式,允许将表达式与多个值进行比较;通用形式,允许表达多个条件语句。下图为后续例子中用到的数据。

如果要在后续子句或语句中使用结果,则 CASE 只能用作 RETURN 或 WITH 的一部分。

Neo4j——一个强大的图谱数据库

简单 CASE 形式

将一个表达式与多个值进行比较,首先计算表达式,并按顺序与 WHEN 子句进行比较,直到找到匹配项。 如果未找到匹配项,则返回 ELSE 子句中的表达式。 但是,如果没有 ELSE 情况且未找到匹配项,则将返回 null,格式如下:

CASE test
  WHEN value THEN result
  [WHEN ...]
  [ELSE default]
END
//test:一个有效的表达式。
//value:一个表达式,其结果将与test进行比较。
//result:这是在值匹配test时作为输出返回的表达式。
//default:如果未找到匹配项,则返回默认值。
    
MATCH (n)
RETURN
CASE n.eyes
  WHEN 'blue'  THEN 1
  WHEN 'brown' THEN 2
  ELSE 3
END AS result
//result
//2
//1
//3
//2
//1

通用 CASE 形式

允许表达多个条件,按顺序求值,直到找到真值,然后使用结果值。 如果未找到匹配项,则返回 ELSE 子句中的表达式。 但是,如果没有 ELSE 情况且未找到匹配项,则将返回 null,示例如下:

CASE
  WHEN predicate THEN result
  [WHEN ...]
  [ELSE default]
END
//predicate:有效的表达式
//result:如果 predicate 的计算结果为真,则这是作为输出返回的表达式。
//如果未找到匹配项,则返回默认值。

MATCH (n)
RETURN
CASE
  WHEN n.eyes = 'blue' THEN 1
  WHEN n.age < 40      THEN 2
  ELSE 3
END AS result
//result
//2
//1
//3
//3
//1

区别

由于两种形式的句法非常相似,有时一开始可能不清楚使用哪种形式。 通过以下查询来说明这种情况,其中如果 n.age 为空,则期望 age_10_years_ago 为 -1:

MATCH (n)
RETURN n.name,
CASE n.age
  WHEN n.age IS NULL THEN -1
  ELSE n.age - 10
END AS age_10_years_ago
n.name	age_10_years_ago

//"Alice" 28
//"Bob" 15
//"Charlie" 43
//"Daniel" <null>
//"Eskil" 31

//正确方式:
MATCH (n)
RETURN n.name,
CASE
  WHEN n.age IS NULL THEN -1
  ELSE n.age - 10
END AS age_10_years_ago

//"Alice" 28
//"Bob" 15
//"Charlie" 43
//"Daniel" -1
//"Eskil" 31

但是,由于此查询是使用简单的 CASE 形式编写的,对于名为 Daniel 的节点,age_10_years_ago 不是 -1,而是 null。 这是因为在 n.age 和 n.age IS NULL 之间进行了比较。 由于 n.age IS NULL 是一个布尔值,而 n.age 是一个整数值,因此永远不会采用 WHEN n.age IS NULL THEN -1 分支。 这导致 ELSE n.age - 10 分支被取而代之,返回 null。

当使用简单的 CASE 形式时,记住在 Cypher 中 null = null 会产生 null 是很有用的。

MATCH (n)
RETURN n.name,
CASE n.age
  WHEN null THEN -1
  ELSE n.age - 10
END AS age_10_years_ago

//"Alice" 28
//"Bob" 15
//"Charlie" 43
//"Daniel" <null>
//"Eskil" 31

由于 null = null 不产生 true,WHEN null THEN -1 分支永远不会被采用,导致 ELSE n.age - 10 分支被采用,返回 null。

4、结果处理

Neo4j——一个强大的图谱数据库

过滤结果

可以在 WHERE 中对 MATCH 的结果进行过滤:

MATCH (m:Movie)
WHERE m.title = "The Matrix"
RETURN m

更好的方式是,在 MATCH 中指定更细致的条件:

MATCH (m:Movie { title: "The Matrix" })
RETURN m

WHERE 子句中可以使用正则表达式:

MATCH (p:Person)-[r:ACTED_IN]->(m:Movie)
WHERE p.name =~ "K.+" OR m.released > 2000 OR "Neo" IN r.roles
RETURN p,r,m

p.name =~ "K.+" 表示 name 以 K 开头。

WHERE 中还可以指定一个模式,可以过滤掉符合或者不符合这个模式的结果。

MATCH (p:Person)-[:ACTED_IN]->(m)
WHERE NOT (p)-[:DIRECTED]->()
RETURN p,m

返回结果

可以对结果整体来处理:

MATCH (:Person)
RETURN count(*) AS people
| people |
| 3      |
1 row

使用 DISTINCT 滤除重复: count(DISTINCT role)

count 会将其他列来进行分组,下面例子中会使用 actor,director 作为分组的键值:

MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)<-[:DIRECTED]-(director:Person)
RETURN actor,director,count(*) AS collaborations

对结果排序

ORDER BY 可以基于任何可以访问到的变量、属性进行排序,默认是正序,如需倒序,使用关键词 DESC

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
RETURN a,count(*) AS appearances
ORDER BY appearances DESC;

Collect

使用 collect 可以将多个匹配收集到一个数组中。

MATCH (m:Movie)<-[:ACTED_IN]-(a:Person)
RETURN m.title AS movie, collect(a.name) AS cast, count(*) AS actors

5、组合查询

UNION

UNION 可以将两个查询结果合并起来:

MATCH (actor:Person)-[r:ACTED_IN]->(movie:Movie)
RETURN actor.name AS name, type(r) AS acted_in, movie.title AS title
UNION
MATCH (director:Person)-[r:DIRECTED]->(movie:Movie)
RETURN director.name AS name, type(r) AS acted_in, movie.title AS title
+-------------------------------------------------+
| name              | acted_in   | title          |
+-------------------------------------------------+
| "Tom Hanks"       | "ACTED_IN" | "Cloud Atlas"  |
| "Tom Hanks"       | "ACTED_IN" | "Forrest Gump" |
| "Robert Zemeckis" | "DIRECTED" | "Forrest Gump" |
+-------------------------------------------------+
3 rows

WITH

WITH 能将多个语句连接起来,就像管道一样,前一个语句的输出作为下一个语句的输入:

MATCH (person:Person)-[:ACTED_IN]->(m:Movie)
WITH person, count(*) AS appearances, collect(m.title) AS movies
WHERE appearances > 1
RETURN person.name, appearances, movies

6、子查询

子查询表达式可以出现在表达式有效的任何地方。 子查询有一个范围,由左大括号和右大括号 { 和 } 表示。 在外部范围内定义的任何变量都可以在子查询自己的范围内引用。 在子查询内部引入的变量不是外部范围的一部分,因此不能在外部访问。

具体案例传送门:Subquery expressions

7、标签表达式

通过一张图说明标签表达式是否匹配关系:

Neo4j——一个强大的图谱数据库

标签的示例传送门:Label expressions

8、关系表达式

通过一张图说明关系表达式是否匹配关系:

Neo4j——一个强大的图谱数据库

标签的示例传送门:Relationship type expressions

9、索引

对节点的某个属性建立索引,之后使用该属性来进行查询时,能够加快查询速度。

CREATE INDEX ON :Actor(name)

MATCH (actor:Actor { name: "Tom Hanks" })
RETURN actor;          

当需要通过某个属性查询所有满足条件的节点时,分别在各不同 label 的节点上建立索引,就不起作用了。

MATCH (n:{ name:"xxx" }) RETURN n

上面这条语句会很慢,他需要遍历所有的节点。为此,可以给所有节点增加一个共有的 label,然后建立索引。

// 给所有节点增加一个 label 叫做 Node
match (n) set n :Node

// 在 Node 的 name 属性上建立索引
create index on :Node(name)