只会MySQL中间表多对多?不懂这个哪来的勇气做架构?

说到图计算,很多人会觉得离自己很远。嗯,很多人大概的想法是这样的。

呃...我得把《离散数学》中的图论学一遍。

嗯...得学无向图、有向图、加权图、循环图、二部图、欧拉图、哈密顿图、平面图。

哈...最短路径、关键路径、DFS、BFS、生成树、PageRank、社群算法。

艹...我还是乖乖回去打王者、洗脚按摩吧。

理论的东西是蛮重要的,但如果你是一名应用型开发人员。用MySQL/Oracle的时候,95%你没有看一遍集合论、二元关系论。其实集合论、二元关系也不简单,一样有很多的理论作为背后的支撑,只是我们平时根本没有留意罢了。所以,但眼下,我们是一名应用开发人员,不管你是数据开发、还是业务系统开发,图相关的技术是不能不懂的,因为你发现,图可以用在很多地方。

因为小猴的项目用到了Neo4j(我喜欢称它为 '尼奥——对,没错,就是黑客帝国的救世主尼奥),所以就花了一点时间。把图数据库Neo4j和声明式图检索语言Cypher分享给大家。

image-20210130164721798

图数据模型

2021年1月初,小猴写了一篇《80%的后端开发不知道的融合数据模型!》https://mp.weixin.qq.com/s/ghUjARAp4fm9195BOH9QJg。 其中提到了,数据与数据之间的关系决定应该使用什么样的数据模型来组织。如果数据之间存在大量的多对多关系,虽然我们可以在关系模型中处理,但如果数据中的连接越来越复杂,此次就将数据建模为图会更加自然。

学习Neo4j和Cypher之前,先来做一些热身运动。

图模型

图(Graph)由两种对象构成:顶点、和边。

例如:

image-20210130154623605

  • 顶点通常表示某个实体
  • 最简单的图就是只有一个顶点。下面就是一个顶点的图。

image-20210130154853804

可以将多种数据建模为图,典型的场景包括:

  • 社交关系图:顶点是人,边是表示彼此认识

  • 网页图:顶点是网页,边表示网页可以跳转到其他的HTML网页

  • 公路或者铁路图:顶点是路口,边表示路口与路口之间的道路或者铁路。

有了图之后,就可以在图上运行一些经典的算法。例如:汽车导航系统搜索两点之间的最短路径、PageRank可以在大量的网页图中确定哪些网页最受欢迎,并对显示的结果排名。

上面的三个场景,每个顶点都是同一类的事物(分别是人、网页、或路口),但图不局限于同类数据。图的强大之处:可以将完全不同类型的对象存储起来,并提供一致的操作方法。例如:Facebook维护具有不同类型的顶点和边的Graph。例如:顶点代表用户、位置、事件、签到、评论。边代表哪些人是朋友、在什么地方签到的、谁评论的帖子、谁参加了哪些活动等等。

属性图

我们一般用的图其实都是属性图,顾名思义,属性图就是顶点和边都带有属性。在属性图模型中,每个顶点包括:

  • 唯一标识符
  • 描述顶点的标签
  • 一组输出的边
  • 一组输入的边
  • 属性集(键值对)

image-20210130161841197

每个边包括:

  • 唯一标识符
  • 边的开始顶点
  • 边的结束顶点
  • 描述两个顶点之间关系的标签
  • 属性集(键值对)

image-20210130155429338

标签是可用于将顶点按照标签分组。例如:用有代表用户的顶点标记为:label:User。有了这个,就可以方便地使用Neo4j在用户顶点上执行操作,例如:查找指定名称的所有用户。在Neo4j中,标签是可以动态添加、删除的。可以用标签来标识一些临时状态。一个顶点可以添加多个标签。

image-20210130155752691

边(即关系)将两个顶点连接起来,将顶点组织成结构。每一种关系都有类型,例如:下面的图的关系类型为ACTED_IN,该关系属性带有一个roles属性。

image-20210130160137070

顶点与自身也是可以有关系的。

image-20210130160014115

设计图数据模型

在关系数据库中,设计数据模型直接会影响SQL语句编写的难易度、清晰度,数据模型对存储的数据结构是非常重要的。而在图数据库中同样适用。关系型数据库一般都是有schema的,而Neo4j是无schema,所以如果业务不断调整,Neo4j也是能够快速适应的。

所谓图数据建模就是用户将业务数据描述为节点的连通图、属性和标签关系的过程。以往在设计关系型模型上,我们先得大体的列出有哪些数据,然后规范化,分开建立表结构。而图数据建模画出来什么样的图,就可以直接用于建模。

image-20210130161222173

基于上面图,再进一步完善,添加标签和更多属性。

image-20210130161435292

查询语言

命令式查询语言

我们用高级语言编写代码搜索数据时是这样的。例如:检索价格大于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:

image-20210124163103299

简单解释下:

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,是数据存储的时候就是彼此连接的。不像其他库,物理分离,通过计算层来做关联。

image-20210130170404512

Neo4j提供开源的社区版,也有闭源高级版本。Neo4j是基于Java来实现的,它可以通过HTTP或者二进制协议,用Cypher QL操作图数据。

PS:Neo4j一定是和The Matrix有关系的。大家看查询语言的命名(Cypher)就知道了。

image-20210130165731808

历史

image-20210130170958064

可以看到,Neo4j发布基本是比较稳定的。从4.0.11开始支持Java 11。

结构

与传统的数据库按照行、列、表的结构不同,Neo4j使用的是灵活的图结构,这种结构是由数据记录之间的存储关系来定义的。Neo4j每个数据节点都可以存储指向与其连接的所有节点的引用,可以更快地执行复杂的连接查询。

image-20210124172933072

在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中有多种与图数据库进行交互和使用的方法。

image-20210124222033576

Neo4j现在做成了一个图平台,各种用户都可以基于平台来工作。不论是开发人员、管理员、大数据开发、数据科学家、BI开发等等。在安装Neo4j之前,我们先来了解下Neo4j里面的一些组件。

Neo4j图数据库

这是核心的图数据库,用于存储和检索数据。有两种版本供选择。

image-20210130173506035

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

http://node1:7474/

image-20210124223103711

默认用户名:neo4j、密码:neo4j。然后修改密码。

image-20210124223903626

系统数据库和默认数据库

image-20210126000115373

所有的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查询的是默认的图,也可以自己声明是哪个图。

image-20210125235752646

Database

数据库提供在磁盘、内存中存储和检索的机制。

快速入门

接下来,我们基于Cypher来构建下面这张图。

image-20210125230540439

创建顶点并建立关系

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查询语言

image-20210130173926934

Cypher图对象

1、Cypher模式匹配

  • Neo4j的图由节点和关系组成,节点和关系也有关联的属性、标签。(和我们之前说的属性图是一回事)
  • 图模型中最有价值、功能最强大的pattern(模式)也是由节点和关系组成。

Cypher很大程度也是基于模式的,它旨在识别出数据中的不同模式。学习Cypher后,会发现其实Cypher很易读,本身也是基于英语自然语言而来。语法清晰易于理解。

2、在Cypher中表示节点

图是由节点和关系组成。节点是图中的数据实体,可以直接通过节点的名字来获取节点。

image-20210126230210511

例如:以上就是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)

image-20210126231657481

如果是无向关系,可以使用两个破折号表示(--)。如果数据是以一种有向关系存储,但查询的时候指定了错误的方向,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

image-20210128235030773

只返回Jennifer工作的公司:

MATCH (j {name: 'Jennifer'}) -[:WORKS_FOR]-> (c)
RETURN c.name

image-20210128235118382

给返回值取别名

MATCH (j {name: 'Jennifer'}) -[:WORKS_FOR]-> (c)
RETURN c.name as company_name

image-20210128235255799

插入操作

为了能够学习之后的基于Cypher的查询,先来学习下如何使用Cypher来进行插入操作。Cypher中要插入数据和SQL中的insert语句是比较类似的,只不过Cypher使用的是CREATE关键字,它使用CREATE来创建节点、关系、模式。

image-20210127224032275

示例中,有一个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。很两步来操作:

  1. 创建一个Mark的Person节点
  2. 建立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

image-20210127231715781

注意:

如果图节点没有显示名字,点击左上角的图例,然后点击下name即可

image-20210127230257433

更新操作

如果图中已经有了一个节点或者是关系,我们想要修改节点、或者关系的属性。在SQL中,我们可以通过UPDATE + SET + WHERE来实现。而在Cypher中,我们可以通过 MATCH + SET来实现。

1、给Jennier设置生日值

MATCH (p:Person {name: 'Jennifer'})
SET p.birthday = date('1980-01-01')
RETURN p

image-20210127232222649

如果要修改birthday,直接使用同样的方式更新即可。也可以使用同样的方式来更新关系的属性,例如:我们来更新Jennifer在Neo4j公司的开始工作时间。

MATCH (p:Person {name: 'Jennifer'}) -[rel:WORKS_FOR]-> (:Company {name:'Neo4j'})
SET rel.since = date({year: 2018})
RETURN rel

image-20210127232908370

也可以让Neo4j返回图视图:

image-20210127233036945

删除操作

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

image-20210127235914052

注意,此处,咱们使用了两个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

image-20210128001202207

过滤查询

针对图查询,大多数都是复杂的输入、和复杂地检索。我们就需要编写查询来检索符合复杂条件的数据。和SQL一样,Cypher也可以使用where子句来提供更大范围的检索。

例如:检查姓名为Jennifer的人。

MATCH (p:Person)
WHERE p.name = 'Jennifer'
RETURN p

f

反向选择

// 获取名字不为Jennifer的人
MATCH (p:Person)
WHERE not p.name = 'Jennifer'
RETURN p

image-20210130001814388

范围查询

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(*)

image-20210130004432122

// 统计所有节点的数量
MATCH (p)
RETURN count(p)

image-20210130004532687

聚合值

使用collect()函数,可以将匹配的元素放在一个列表中。

// 找出所有人的朋友
MATCH (j:Person {name: 'Jennifer'}) -[:IS_FRIENDS_WITH]-> (friend:Person)
RETURN j.name, collect(friend.name)

image-20210130004923524

对列表进行计数

// 找出所有人的朋友,并返回朋友的个数
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

image-20210130005131885

使用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
  • 如果模式较长,在箭头的位置进行换行

参考文献:

posted @ 2021-02-21 16:13  斜杠代码日记  阅读(791)  评论(0编辑  收藏  举报