只会MySQL中间表多对多?不懂这个哪来的勇气做架构?
说到图计算,很多人会觉得离自己很远。嗯,很多人大概的想法是这样的。
呃...我得把《离散数学》中的图论学一遍。
嗯...得学无向图、有向图、加权图、循环图、二部图、欧拉图、哈密顿图、平面图。
哈...最短路径、关键路径、DFS、BFS、生成树、PageRank、社群算法。
艹...我还是乖乖回去打王者、洗脚按摩吧。
理论的东西是蛮重要的,但如果你是一名应用型开发人员。用MySQL/Oracle的时候,95%你没有看一遍集合论、二元关系论。其实集合论、二元关系也不简单,一样有很多的理论作为背后的支撑,只是我们平时根本没有留意罢了。所以,但眼下,我们是一名应用开发人员,不管你是数据开发、还是业务系统开发,图相关的技术是不能不懂的,因为你发现,图可以用在很多地方。
因为小猴的项目用到了Neo4j(我喜欢称它为 '尼奥——对,没错,就是黑客帝国的救世主尼奥),所以就花了一点时间。把图数据库Neo4j和声明式图检索语言Cypher分享给大家。
图数据模型
2021年1月初,小猴写了一篇《80%的后端开发不知道的融合数据模型!》https://mp.weixin.qq.com/s/ghUjARAp4fm9195BOH9QJg。 其中提到了,数据与数据之间的关系决定应该使用什么样的数据模型来组织。如果数据之间存在大量的多对多关系,虽然我们可以在关系模型中处理,但如果数据中的连接越来越复杂,此次就将数据建模为图会更加自然。
学习Neo4j和Cypher之前,先来做一些热身运动。
图模型
图(Graph)由两种对象构成:顶点、和边。
例如:
- 顶点通常表示某个实体
- 最简单的图就是只有一个顶点。下面就是一个顶点的图。
可以将多种数据建模为图,典型的场景包括:
-
社交关系图:顶点是人,边是表示彼此认识
-
网页图:顶点是网页,边表示网页可以跳转到其他的HTML网页
-
公路或者铁路图:顶点是路口,边表示路口与路口之间的道路或者铁路。
有了图之后,就可以在图上运行一些经典的算法。例如:汽车导航系统搜索两点之间的最短路径、PageRank可以在大量的网页图中确定哪些网页最受欢迎,并对显示的结果排名。
上面的三个场景,每个顶点都是同一类的事物(分别是人、网页、或路口),但图不局限于同类数据。图的强大之处:可以将完全不同类型的对象存储起来,并提供一致的操作方法。例如:Facebook维护具有不同类型的顶点和边的Graph。例如:顶点代表用户、位置、事件、签到、评论。边代表哪些人是朋友、在什么地方签到的、谁评论的帖子、谁参加了哪些活动等等。
属性图
我们一般用的图其实都是属性图,顾名思义,属性图就是顶点和边都带有属性。在属性图模型中,每个顶点包括:
- 唯一标识符
- 描述顶点的标签
- 一组输出的边
- 一组输入的边
- 属性集(键值对)
每个边包括:
- 唯一标识符
- 边的开始顶点
- 边的结束顶点
- 描述两个顶点之间关系的标签
- 属性集(键值对)
标签是可用于将顶点按照标签分组。例如:用有代表用户的顶点标记为:label:User。有了这个,就可以方便地使用Neo4j在用户顶点上执行操作,例如:查找指定名称的所有用户。在Neo4j中,标签是可以动态添加、删除的。可以用标签来标识一些临时状态。一个顶点可以添加多个标签。
边(即关系)将两个顶点连接起来,将顶点组织成结构。每一种关系都有类型,例如:下面的图的关系类型为ACTED_IN,该关系属性带有一个roles属性。
顶点与自身也是可以有关系的。
设计图数据模型
在关系数据库中,设计数据模型直接会影响SQL语句编写的难易度、清晰度,数据模型对存储的数据结构是非常重要的。而在图数据库中同样适用。关系型数据库一般都是有schema的,而Neo4j是无schema,所以如果业务不断调整,Neo4j也是能够快速适应的。
所谓图数据建模就是用户将业务数据描述为节点的连通图、属性和标签关系的过程。以往在设计关系型模型上,我们先得大体的列出有哪些数据,然后规范化,分开建立表结构。而图数据建模画出来什么样的图,就可以直接用于建模。
基于上面图,再进一步完善,添加标签和更多属性。
查询语言
命令式查询语言
我们用高级语言编写代码搜索数据时是这样的。例如:检索价格大于10000的商品。
public void getGoodsGreaterThan10000() {
ArrayList<Goods> gt10000List = new ArrayList<>;
for(Goods goods : allGoodsList) {
if(goods.price > 10000) {
gt10000List.add(goods);
}
}
return gt10000List;
}
这种代码都是一行一行地执行、遍历,检查条件。在进行数据处理时,其实是比较繁琐的。所以,大部分人做数据分析的时候不会写Java。我们把这种语言称之为命令式查询语言。
而是使用SQL这种语言。那SQL和Java有什么不一样吗?
声明式查询语言
在基于关系模型时,一般会使用SQL来查询、检索数据。SQL是一种声明式的查询语言。上面的Java代码,如果我们使用SQL,一个SELECT就完成了。
SELECT * FROM tbl_goods WHERE price = '10000';
在声明式查询语言中,只需要指定我们所需要的数据的模式(满足特定的条件),同时指定如何转换数据(例如:排序、分组、聚合)。根本无需考虑如何实现这些功能。数据库系统会自动帮助我们实现,例如:进行查询优化、如何使用索引、如何关联、以及如何执行查询各个部分的顺序。
声明性查询语言使用起来比命令性API更简洁,更易于使用。它隐藏了数据库引擎的实现细节。
MapReduce查询
MapReduce是用于批量处理大量数据的编程模型。MapReduce既不是声明式查询语言,也不是完全命令式查询API,它介于二者之间:查询的逻辑用代码段表示,处理框架会反复调用它们。基于Map和Reduce模型,然后用开发语言就可以实现数据的查询。
Cypher Query Language
Cypher是专门操作图数据库的声明式查询语言,在Neo4j中,使用Cypher可以非常方便的检索、查询图。Cypher使用的是黑客帝国中的角色来命名的。看一下Cypher Query:
简单解释下:
CREATE /* 表示创建图对象,顶点、关系、模式 */
(NAmerica /* 顶点标识符 */: Location /* 顶点类型 */ { name: 'xxx' /* 属性 */, type: 'xxxx'},
...
(Idaho /* 引用之前的顶点 */) -[:WITH /* 关系类型 */] -> /* 定义关系 */ (USA /* 应用顶点 */) ...
每个顶点都有一个符号名称,例如:NAmerica、USA,以及可以在顶点之间使用箭头,(Idaho) -[:WITHIN]-> (USA)来创建标记为WITHIN的边。Idaho边的头结点、USA为边的尾节点。
现在大家看着会有点蒙圈,等看完这篇就明白啦。
Neo4j简介
介绍
Neo4j旨在有效存储、处理和查询关联在一起的数据。Neo4j(Network Exploration and Optimization 4 Java)是一个图数据库管理系统(GDBMS)。它是一个具有Native(翻译成:原生)的图存储、图数据处理功能,而且支持ACID的数据库。
之所以称之为Native,是数据存储的时候就是彼此连接的。不像其他库,物理分离,通过计算层来做关联。
Neo4j提供开源的社区版,也有闭源高级版本。Neo4j是基于Java来实现的,它可以通过HTTP或者二进制协议,用Cypher QL操作图数据。
PS:Neo4j一定是和The Matrix有关系的。大家看查询语言的命名(Cypher)就知道了。
历史
可以看到,Neo4j发布基本是比较稳定的。从4.0.11开始支持Java 11。
结构
与传统的数据库按照行、列、表的结构不同,Neo4j使用的是灵活的图结构,这种结构是由数据记录之间的存储关系来定义的。Neo4j每个数据节点都可以存储指向与其连接的所有节点的引用,可以更快地执行复杂的连接查询。
在Neo4j中,所有存储的数据都以节点、边或者属性的形式存储。每个节点和边都可以包含任意数量的属性。节点和边还可以被打上标签,通过标签可以缩小检索范围。
Cypher查询语言
Cypher是一种功能强大、基于图的查询语言。它比大量的SQL Join更简单、更容易编写。因为Neo4j没有表的概念,所以也不用考虑JOIN。Cypher是一种声明式语言,重点表达从图中检索什么样的数据,而不是如何获取数据。
参考下面的SQL语句:
SELECT p.name
FROM tbl_product AS p
JOIN tbl_prod_category c ON (p.cID = c.cID AND c.cName = "家用电器")
JOIN tbl_prod_category c1 ON (p.cID = c1.cID)
JOIN tbl_prod_category c2 ON (c1.ParentID = c2.cID AND c2.cName = "家用电器")
JOIN tbl_prod_c c3 ON (p.cID = c3.cID)
JOIN tbl_prod_c c4 ON (c3.ParentID = c4.cID)
JOIN tbl_prod_c c5 ON (c4.ParentID = c5.cID AND c5.cName = "家用电器")
;
而使用Cypher语言,如下:
MATCH (p:Product)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]->(:ProductCategory {name:"家用电器"})
RETURN p.name
Neo4j图平台组件
Neo4j中有多种与图数据库进行交互和使用的方法。
Neo4j现在做成了一个图平台,各种用户都可以基于平台来工作。不论是开发人员、管理员、大数据开发、数据科学家、BI开发等等。在安装Neo4j之前,我们先来了解下Neo4j里面的一些组件。
Neo4j图数据库
这是核心的图数据库,用于存储和检索数据。有两种版本供选择。
1、社区版本
社区版是Neo4j的全功能版本,适用于部署在单机上。支持Neo4j的关键功能,例如:支持ACID的事务、Cypher和编程API,比较适合学习Neo4j,自己开发的项目或者小型的应用。
2、企业版本
企业版提供了所有的功能,包括备份、集群、故障转移等功能。如果项目对规模、可用性要求较高,就需要使用企业版了。
Neo4j Destop
用于管理Neo4j的本地实例应用程序。
Neo4j Broswer
Neo4j的在线浏览器界面,用于查询和查看数据库中的数据。使用Cypher查询语言的基本可视化功能。
Neo4j Bloom
业务用户的可视化工具,不需要任何代码、编程技能就可以查看和分析数据。
Neo4j Aura
由Neo4j管理的用于公有云上的图数据库。
Graph Data Science
官方支持的库,用于使用Neo4j执行图算法,并针对企业工作负载和管道进行优化。
安装Neo4j
安装
Neo4j的安装非常简单。
1、上传并解压Neo4j
[root@node1 ~]# ll
total 0
-rw-r--r-- 1 root root 0 Jan 24 21:58 Neo4j-community-4.2.2-unix.tar.qbl
[root@node1 ~]# tar -xvf Neo4j-community-4.2.2-unix.tar -C /opt/
[root@node1 opt]# ll
total 0
drwxr-xr-x 10 501 games 156 Dec 2 17:30 flink-1.12.0
drwxr-xr-x 7 root root 245 Dec 27 11:31 jdk1.8.0_241
drwxr-xr-x 12 111 input 230 Dec 23 00:32 Neo4j-community-4.2.2
2、安装Oracle JDK 11。注意当前的4.2.2版本,要求JDK 11。
ERROR! Neo4j cannot be started using java version 1.8.0_241.
* Please use Oracle(R) Java(TM) 11, OpenJDK(TM) 11 to run Neo4j.
* Please see https://Neo4j.com/docs/ for Neo4j installation instructions.
[root@node1 ~]# ll
total 291600
-rw-r--r-- 1 root root 181727980 Jan 24 22:08 jdk-11.0.10_linux-x64_bin.tar.gz
-rw-r--r-- 1 root root 116863895 Jan 24 22:00 Neo4j-community-4.2.2-unix.tar
tar -xvzf jdk-11.0.10_linux-x64_bin.tar.gz -C /opt/
# 配置JAVA_HOME
export JAVA_HOME=/opt/jdk-11.0.10
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin
2、设置Neo4j_HOME环境变量
export Neo4j_HOME=/opt/Neo4j-community-4.2.2
export PATH=$Neo4j_HOME/bin:$PATH
3、配置Neo4j绑定的机器名
vim $Neo4j_HOME/conf/neo4j.conf
dbms.default_listen_address=0.0.0.0
dbms.default_advertised_address=node1
4、启动
bin/neo4j console
5、打开Neo4j Web UI
默认用户名:neo4j、密码:neo4j。然后修改密码。
系统数据库和默认数据库
所有的Neo4j服务器都包含了一个system的内置数据库,这个system库和其他库不一样。里面存储的是系统数据,无法进行图查询。
还有一个默认的库就是:neo4j
库了。在neo4j.conf中可以找到以下配置:
# The name of the default database
#dbms.default_database=neo4j
几个术语
DBMS
Neo4j数据库管理系统能够包含和管理数据库中的多个图。客户端(例如:Cypher-shell)可以连接到DBMS,并打开会话。基于会话可以访问任意DBMS中的图。
Graph
RDBMS中的数据模型是以关系表组织的。而Neo4j中的数据模型则是图。每个数据库只有一个图。很多的一些管理命令是基于数据库名称来实现的。在一个客户端会话中,使用Cypher查询的是默认的图,也可以自己声明是哪个图。
Database
数据库提供在磁盘、内存中存储和检索的机制。
快速入门
接下来,我们基于Cypher来构建下面这张图。
创建顶点并建立关系
CREATE (john: Person {name: 'John'})
CREATE (sara: Person {name: 'Sara'})
CREATE (joe: Person {name: 'Joe'})
CREATE (maria: Person {name: 'Maria'})
CREATE (steve: Person {name: 'Steve'})
CREATE (john) -[:FRIEND]-> (joe) -[:FRIEND]-> (steve)
CREATE (john) -[:FRIEND]-> (sara) -[:FRIEND]-> (maria);
查询数据<一>
// 查询名字为 John,且他的朋友,以及他的朋友的朋友
MATCH (john {name:'John'}) -[:FRIEND]->() -[:FRIEND]->(fof)
RETURN john.name, fof.name
+----------------------+
| john.name | fof.name |
+----------------------+
| "John" | "Maria" |
| "John" | "Steve" |
+----------------------+
查询数据<二>
// 查询朋友名称中以S开头的人
MATCH (user) -[:FRIEND]-> (follower)
WHERE follower.name =~ 'S.*'
RETURN user.name, follower.name
+---------------------------+
| user.name | follower.name |
+---------------------------+
| "Joe" | "Steve" |
| "John" | "Sara" |
+---------------------------+
删除所有关系
// 删除所有关系
MATCH () -[r]-> ()
DELETE r
;
0 rows available after 12 ms, consumed after another 0 ms
Deleted 4 relationships
删除所有节点
// 删除所有节点
MATCH (p)
DELETE p
;
0 rows available after 5 ms, consumed after another 0 ms
Deleted 5 nodes
Cypher查询语言
Cypher图对象
1、Cypher模式匹配
- Neo4j的图由节点和关系组成,节点和关系也有关联的属性、标签。(和我们之前说的属性图是一回事)
- 图模型中最有价值、功能最强大的pattern(模式)也是由节点和关系组成。
Cypher很大程度也是基于模式的,它旨在识别出数据中的不同模式。学习Cypher后,会发现其实Cypher很易读,本身也是基于英语自然语言而来。语法清晰易于理解。
2、在Cypher中表示节点
图是由节点和关系组成。节点是图中的数据实体,可以直接通过节点的名字来获取节点。
例如:以上就是4个节点。分别是:Michael、Graphs、Jennifer、Neo4j。而为了描述Cypher中的节点,我们可以用括号把节点括起来(括号看起来和节点的圆形比较类似)。
3、节点变量
如果后续要应用某个节点,可以给这个节点设置为一个变量,例如:(p)、(t)这样。我们可以像在Java中一样,来命名节点。后续的查询中,我们可以直接使用这些名字。如果使用的是一个()空括号,表示创建的是一个匿名节点。
4、节点标签
创建节点时,可以给节点指定标签,这样将来容易放在一组里。有点类似于SQL,可以指定查询哪张表。而在图中,可以指定查询某一个标签。如果在使用Cypher查询时,没有指定标签,这种情况Cypher会查询数据库中的所有节点。如果有一个很大的图,则将效率低下。
在以下下面的几组描述:
() // 匿名节点,可以在当前数据中应用任意的节点
(p:Person) // 创建了变量p、并指定了节点的标签为Person
(:Technology) // 没有创建变量、但指定了Technology标签
(work:Company) // 使用完整的变量名、并指定了节点的标签Company
描述关系
Cypher在两个节点之间使用 --> 或者 <-- 来表示关系。并且,可以将关系类型、或者相关的属性放在箭头内部方括号中。例如:
(p1) -[:FRIEND]-> (p2)
如果是无向关系,可以使用两个破折号表示(--)。如果数据是以一种有向关系存储,但查询的时候指定了错误的方向,Cypher不会返回任何查询结果。如果不确定方向,最好还是使用无向关系检索。
// 按照(p) --> (t)这个方向存储节点、关系
CREATE (p:Person)-[:LIKES]->(t:Technology)
// 查询反的方向将获取不到任何数据
MATCH (p:Person)<-[:LIKES]-(t:Technology)
// 可以不指定方向来查询数据
MATCH (p:Person)-[:LIKES]-(t:Technology)
关系类型
关系类型用于对关系进行分类,并给关系添加一定含义。找图中,关系描述了节点如何相互连接、相互关联。通常使用动作、或者是动词来识别关系,这样可以遵循一个良好的命名约定。
例如:
- [:LIKES]——在关系的另外一侧放置节点才会有意义
- 同理,[:IS_FRIEND_WITH]、[:WORKS_FOR]
关系变量
就像节点一样,如果我们在后续的查询中需要引用关系,也可以给关系定义一个变量,例如:[r:IS_FRIENDS_WITH]或者[rel:xxx]。如果后续无需引用关系,可以使用 --+、+-->+、+<--来指定匿名关系。
-[:LIKES]->
// 加冒号表示的是关系类型,而不加冒号表示的是定义变量
-[LIKES]-->
节点或者关系属性
节点和关系都可以添加属性。属性就是key-value键值对。在Cypher中,可以在节点、或者关系的括号内使用好括号来表示属性。例如:
// 节点属性
(p:Person {name:'Jennifer'})
// 关系属性
-[ref: IS_FRIENDS_WITH {since: 2018}]
Cypher中的模式
- 节点和关系构成了模式的基础,可以通过节点和关系来定义简单、或者是复杂的模式。
- 模式是图最强大的功能。
- 在Cypher中,可以将模式写成一个连续的路径、也可以拆分为更小的模式,用逗号隔开
下面的代码就是用节点、关系的表达式组合而来的一个模式:
(p:Person {name: "Jennifer"})-[rel:LIKES]->(g:Technology {type: "Graphs"})
它描述的是:Jennifer喜欢Graph。上述代码定义了一个模式,但还没有指定与该模型关联的操作。
查询操作
要在Cypher中查询数据,需要使用以下两个关键字:
- MATCH
- RETURN
MATCH
MATCH关键字用于搜索图中现有的节点、关系、标签、属性或者模式。
RETURN
RETURN关键字表示从Cypher的查询返回结果。可以指定Cypher返回的是节点、关系、属性或者模式。但写入数据的时候不需要RETURN,但如果读取就需要了。
例如:在图中找到带有Person标签的节点。
MATCH (p:Person) RETURN p
在图中查找名字为Jennifer的节点。
MATCH(p {name: 'Jennifer'})
RETURN p
在图中查找Jennifer工作的公司对应的节点:
MATCH (j {name: 'Jennifer'}) -[:WORKS_FOR]-> (c)
RETURN c
只返回Jennifer工作的公司:
MATCH (j {name: 'Jennifer'}) -[:WORKS_FOR]-> (c)
RETURN c.name
给返回值取别名
MATCH (j {name: 'Jennifer'}) -[:WORKS_FOR]-> (c)
RETURN c.name as company_name
插入操作
为了能够学习之后的基于Cypher的查询,先来学习下如何使用Cypher来进行插入操作。Cypher中要插入数据和SQL中的insert语句是比较类似的,只不过Cypher使用的是CREATE关键字,它使用CREATE来创建节点、关系、模式。
示例中,有一个Person节点(Jennifer),它喜欢图,而且他是Michael的朋友、并且在Neo4j公司工作。
CREATE (jennifer:Person { name: 'Jennifer'})
CREATE (michael:Person { name: 'Michael'})
CREATE (graph: Technology { type: 'Graphs'})
CREATE (neo4j:Company {name: 'Neo4j'})
CREATE (jennifer) -[:LIKES]-> (graph)
CREATE (jennifer) -[:IS_FRIENDS_WITH {since: 2018 }]-> (michael)
CREATE (jennifer) -[:WORKS_FOR]-> (neo4j)
现在我们往图中添加一个Jennifer的朋友——Mark。很两步来操作:
- 创建一个Mark的Person节点
- 建立Jennifer和Mark的朋友关系
创建一个Mark的Person节点
CREATE (friend:Person {name:'Mark'})
RETURN friend
创建Jennifer和Mark的关系
先使用MATCH,将两个节点查询出来,然后再创建关联关系即可。
MATCH (jennifer:Person {name: 'Jennifer'})
MATCH (mark:Person {name: 'Mark'})
CREATE (jennifer) -[rel:IS_FRIENDS_WITH]-> (mark)
注意:
此处要编写两个MATCH,Neo4j不会检查该节点是否在图中是否存在,如果没有MATCH,则会自动添加节点。就可能会创建出来新的节点。
查询下jennifer节点:
MATCH (j:Person {name: 'Jennifer'})
MATCH (m:Person {name: 'Mark'})
RETURN j,m
注意:
如果图节点没有显示名字,点击左上角的图例,然后点击下name即可
更新操作
如果图中已经有了一个节点或者是关系,我们想要修改节点、或者关系的属性。在SQL中,我们可以通过UPDATE + SET + WHERE来实现。而在Cypher中,我们可以通过 MATCH + SET来实现。
1、给Jennier设置生日值
MATCH (p:Person {name: 'Jennifer'})
SET p.birthday = date('1980-01-01')
RETURN p
如果要修改birthday,直接使用同样的方式更新即可。也可以使用同样的方式来更新关系的属性,例如:我们来更新Jennifer在Neo4j公司的开始工作时间。
MATCH (p:Person {name: 'Jennifer'}) -[rel:WORKS_FOR]-> (:Company {name:'Neo4j'})
SET rel.since = date({year: 2018})
RETURN rel
也可以让Neo4j返回图视图:
删除操作
Cypher中,也是使用DELETE关键字来删除节点、和关系。Neo4j符合ACID,如果节点仍然存在与之关联的关系,是无法删除节点的。
1、删除关系
要删除一个关系,先要找到关系的start和end节点,然后使用DELETE关键字删除。现在我们来测试下删除:Jennifer和Mark之间的IS_FRIENDS_WITH关系。
MATCH (p {name: 'Jennifer'}) -[rel]-> (c {name:'Mark'})
DELETE rel
2、删除节点
要删除没有任何关系的节点,直接找到对应的节点,然后用DELETE删除即可。和删除关系一样。
MATCH (c {name:'Mark'})
DELETE c
3、删除节点和关系
使用DETACH DELETE会先删除该节点相关的所有关系,然后再删除节点。
MATCH (m:Person {name:'Mark'})
DETACH DELETE m
4、删除属性
我们同样可以使用DELETE来删除属性。但除了DELETE方式以外,还可以使用REMOVE和设置null来删除属性。因为Neo4j中是不会存储null的属性的。例如:现在我们要删除Jennier生日这个属性。
MATCH (m:Person {name: 'Jennifer'})
REMOVE m.birthday
MATCH (m:Person {name: 'Jennifer'})
SET m.birthday = null
使用Merge操作避免重复
Merge表示选择或者插入操作,首先会检查数据库中是否存在数据。如果存在,则Cypher会原样返回,或在现有节点或者关系上更新。如果不存在,则Cypher会创建节点或者关系。
MERGE (mark:Person {name:'Mark'})
RETURN mark
使用MERGE同样可以来查询或者创建一个关系。
MATCH (j:Person {name:'Jennifer'})
MATCH (m:Person {name:'Mark'})
MERGE (j) -[rel:IS_FRIENDS_WITH]-> (m)
RETURN j,m,rel
注意,此处,咱们使用了两个MATCH + MERGE来实现的。我们确实可以使用一个MERGE来实现,但要考虑一个重要的问题:Cypher是绝对不会在模式中既匹配、又创建新的对象,如果它发现某个模式不存在,会将模式中节点、关系都创建出来,这将会出现数据重复。为了避免这种情况,先匹配现有的所有元素,再MERGE元素。
使用MERGE创建模式后,只初始化一次某些属性。如果匹配则更新其他属性,可以使用ON CREATE 和 ON MATCH一起使用来实现。例如:如果Jennifer和Mark存在IS_FRIENDS_WITH关系时,设置since属性,否则更新update属性。
MERGE (j:Person {name: 'Jennifer'}) -[r:IS_FRIENDS_WITH]-> (m:Peron {name: 'Mark'})
ON CREATE SET r.since = date('2018-08-01')
ON MATCH SET r.update = date()
RETURN j,m,r
过滤查询
针对图查询,大多数都是复杂的输入、和复杂地检索。我们就需要编写查询来检索符合复杂条件的数据。和SQL一样,Cypher也可以使用where子句来提供更大范围的检索。
例如:检查姓名为Jennifer的人。
MATCH (p:Person)
WHERE p.name = 'Jennifer'
RETURN p
反向选择
// 获取名字不为Jennifer的人
MATCH (p:Person)
WHERE not p.name = 'Jennifer'
RETURN p
范围查询
MATCH (p:Person)
WHERE 3 <= p.yearsExp <= 7
RETURN p
测试是否存在查询
MATCH (p:Person)
WHERE exists(p.birthdate)
RETURN p.name
匹配部分、模糊查询
//check if a property starts with 'M'
MATCH (p:Person)
WHERE p.name STARTS WITH 'M'
RETURN p.name
//check if a property contains 'a'
MATCH (p:Person)
WHERE p.name CONTAINS 'a'
RETURN p.name
//check if a property ends with 'n'
MATCH (p:Person)
WHERE p.name ENDS WITH 'n'
//正则表达式
MATCH (p:Person)
WHERE p.name =~ 'Jo.*'
RETURN p.name
// IN表达式
MATCH (p:Person)
WHERE p.yearsExp IN [1, 5, 6]
RETURN p.name, p.yearsExp
检查模式匹配情况
//Query1: find which people are friends of someone who works for Neo4j
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}))
RETURN p, r, friend
//Query2: find Jennifer's friends who do not work for a company
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE p.name = 'Jennifer'
AND NOT exists((friend)-[:WORKS_FOR]->(:Company))
RETURN friend.name
可选模式
// 使用OPTIONAL MATCH可以模拟出来OUTER JOIN的效果,如果没有匹配到则值为NULL
//find all people whose name starts with J and who may work for a company.
MATCH (p:Person)
WHERE p.name STARTS WITH 'J'
OPTIONAL MATCH (p)-[:WORKS_FOR]-(other:Company)
RETURN p.name, other.name
复杂查询
//Query1: find who likes graphs besides Jennifer
MATCH (j:Person {name: 'Jennifer'})-[r:LIKES]-(graph:Technology {type: 'Graphs'})-[r2:LIKES]-(p:Person)
RETURN p.name
//Query2: find who likes graphs besides Jennifer that she is also friends with
//使用逗号,匹配多种条件
MATCH (j:Person {name: 'Jennifer'})-[:LIKES]->(:Technology {type: 'Graphs'})<-[:LIKES]-(p:Person),
(j)-[:IS_FRIENDS_WITH]-(p)
RETURN p.name
控制查询处理
在学习SQL的时候,除了基本的查询,还可以对结果进行计数、按照某个字段进行分组,然后统计最大、最小值。Cypher中也提供了类似的功能。
COUNT
可以使用count()函数来对计算结果出现的次数。count有几种使用方法:
- count(n):n出现的次数,不包括控制(有点类似于SQL中的count(字段名))
- count(*):一共的行数
// 统计人标签节点的数量
MATCH (:Person)
RETURN count(*)
// 统计所有节点的数量
MATCH (p)
RETURN count(p)
聚合值
使用collect()函数,可以将匹配的元素放在一个列表中。
// 找出所有人的朋友
MATCH (j:Person {name: 'Jennifer'}) -[:IS_FRIENDS_WITH]-> (friend:Person)
RETURN j.name, collect(friend.name)
对列表进行计数
// 找出所有人的朋友,并返回朋友的个数
MATCH (j:Person {name: 'Jennifer'}) -[:IS_FRIENDS_WITH]-> (friend:Person)
RETURN j.name, collect(friend.name) as friends, size(collect(friend.name)) as num_of_friends
使用WITH简化查询
通过WITH子句,可以将结果定义为变量,然后接着使用。
MATCH (a:Person)-[r:LIKES]-(t:Technology)
WITH a.name AS name, collect(t.type) AS technologies
// 注意,WITH后面的return只能应用name和technologies两个字段
RETURN name, technologies
MATCH (p:Person)-[:IS_FRIENDS_WITH]->(friend:Person)
WITH p, collect(friend.name) AS friendsList, size((friend)-[:IS_FRIENDS_WITH]-(:Person)) AS numberOfFoFs
WHERE numberOfFoFs > 1
RETURN p.name, friendsList, numberOfFoFs
// 使用with来定义常量
//find people with 2-6 years of experience
WITH 2 AS experienceMin, 6 AS experienceMax
MATCH (p:Person)
WHERE experienceMin <= p.yrsExperience <= experienceMax
RETURN p
使用UNWIND来定义循环
//Query9: for a list of techRequirements, look for people who have each skill
WITH ['Graphs','Query Languages'] AS techRequirements
UNWIND techRequirements AS technology
MATCH (p:Person)-[r:LIKES]-(t:Technology {type: technology})
RETURN t.type, collect(p.name) AS potentialCandidates
//Query10: for numbers in a list, find candidates who have that many years of experience
WITH [4, 5, 6, 7] AS experienceRange
UNWIND experienceRange AS number
MATCH (p:Person)
WHERE p.yearsExp = number
RETURN p.name, p.yearsExp
排序
默认为升序
//Query11: for a list of techRequirements, look for people who have each skill - ordered Query9
WITH ['Graphs','Query Languages'] AS techRequirements
UNWIND techRequirements AS technology
MATCH (p:Person)-[r:LIKES]-(t:Technology {type: technology})
WITH t.type AS technology, p.name AS personName
ORDER BY technology, personName
RETURN technology, collect(personName) AS potentialCandidates
//Query12: for numbers in a list, find candidates who have that many years of experience - ordered Query10
WITH [4, 5, 6, 7] AS experienceRange
UNWIND experienceRange AS number
MATCH (p:Person)
WHERE p.yearsExp = number
RETURN p.name, p.yearsExp ORDER BY p.yearsExp DESC
返回唯一结果
//Query13: find people who have a twitter or like graphs or query languages
MATCH (user:Person)
WHERE user.twitter IS NOT null
WITH user
MATCH (user)-[:LIKES]-(t:Technology)
WHERE t.type IN ['Graphs','Query Languages']
RETURN DISTINCT user.name
LIMIT限定返回结果
//Query14: find the top 3 people who have the most friends
MATCH (p:Person)-[r:IS_FRIENDS_WITH]-(other:Person)
RETURN p.name, count(other.name) AS numberOfFriends
ORDER BY numberOfFriends DESC
LIMIT 3
子查询
// 使用UNWIND生成数据
Run in Neo4j Browser
UNWIND [
{ title: "Cypher Basics I",
created: datetime("2019-06-01T18:40:32.142+0100"),
datePublished: date("2019-06-01"),
readingTime: {minutes: 2, seconds: 15} },
{ title: "Cypher Basics II",
created: datetime("2019-06-02T10:23:32.122+0100"),
datePublished: date("2019-06-02"),
readingTime: {minutes: 2, seconds: 30} },
{ title: "Dates, Datetimes, and Durations in Neo4j",
created: datetime(),
datePublished: date(),
readingTime: {minutes: 3, seconds: 30} }
] AS articleProperties
CREATE (article:Article {title: articleProperties.title})
SET article.created = articleProperties.created,
article.datePublished = articleProperties.datePublished,
article.readingTime = duration(articleProperties.readingTime)
- WHERE子句中有子查询
- CALL{}语法返回结果子查询
WHERE子句子查询
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}))
RETURN p, r, friend
EXISTS子查询
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE EXISTS {
MATCH (p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'})
}
RETURN p, r, friend
MATCH (person:Person)-[:WORKS_FOR]->(company)
WHERE company.name STARTS WITH "Company"
AND EXISTS {
MATCH (person)-[:LIKES]->(t:Technology)
WHERE size((t)<-[:LIKES]-()) >= 3
}
RETURN person.name as person, company.name AS company;
CALL {} 子查询
// 使用UNION可以去重后取并集
CALL {
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p
UNION
MATCH (p:Person)
WHERE size((p)-[:IS_FRIENDS_WITH]->()) > 1
RETURN p
}
RETURN p.name AS person, p.birthdate AS dob
ORDER BY dob DESC;
CALL {
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p
UNION
MATCH (p:Person)
WHERE size((p)-[:IS_FRIENDS_WITH]->()) > 1
RETURN p
}
RETURN min(p.birthdate) AS oldest, max(p.birthdate) AS youngest
Cypher语言规范
- Node标签使用大写驼峰命名
- 关系标签以大小、下划线命名
- 不加很好
- 避免使用双引号
- null全部都用小写,布尔值用小写true/false
- 如果模式较长,在箭头的位置进行换行
参考文献: