Neo4j——一个强大的图谱数据库
- 开源 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安装
-
拉取镜像
docker pull neo4j
-
指定目录(以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/
2、生态
Neo4j Browser和Bloom是两个与桌面捆绑在一起的图形应用程序。浏览器用作查询工具,用于针对Neo4j图形数据运行Cypher查询并查看结果。Bloom 用于使用搜索输入可视化图形数据。除此之外,还可以从桌面内的图形应用库中获得其他图形应用。图形应用可以与桌面中当前运行的 DBMS 一起使用。
neo4j browser
neo4j browser 有点类似于 mysql 里面的 workbench 或者 mongo 里面的 RoboMongo,说白了就是一个数据库的客户端,但是 neo4j browser 做得非常友好,甚至看上去,用起来就像是一个成熟的产品一样。
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 中的 SELECT
、WHERE
等子句,在 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
为现有的节点添加关系:
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
匹配
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 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
如果不存在 p
和 m
之间的 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
case表达式
通用条件表达式可以使用 CASE 结构来表达。 Cypher 中存在两种 CASE 变体:简单形式,允许将表达式与多个值进行比较;通用形式,允许表达多个条件语句。下图为后续例子中用到的数据。
如果要在后续子句或语句中使用结果,则 CASE 只能用作 RETURN 或 WITH 的一部分。

简单 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、结果处理

过滤结果
可以在 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、标签表达式
通过一张图说明标签表达式是否匹配关系:
标签的示例传送门:Label expressions
8、关系表达式
通过一张图说明关系表达式是否匹配关系:
标签的示例传送门: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)
转载自:https://juejin.cn/post/7236951503797796921