3.1 Cypher概述
3.1.1 Cypher是什么
Cypher是一种声明式图数据库查询语言,它具有丰富的表现力,能高效地查询和更新图数据。对于初学者Cypher使用相对简单,但其功能还是非常强大,即便是非常复杂的数据库查询也能用Cypher简要地表达出来。这使得用户可以将精力集中在自己所从事的领域,而不用在数据库访问上花太多时间。
Cypher查询语言设计很人性化,既适合开发人员,也适合专业的运营人员(这点尤为重要)。作为一个声明式查询语言,Cypher专注于清晰地表达从图中检索什么,而不是怎么去检索。在这点上,与命令式的Java语言和脚本式的Gremlin语言完全不同。
Cypher博采众长,同时也继承了已有的惯用做法。像WHERE和ORDER BY等大多数关键词均来自于SQL语言。而像模式匹配表达方法借鉴于SPARQL语言,部分聚合(Collection)语法来源于像Haskell和Python语言。
Cypher借鉴了SQL语言的结构——查询可由各种各样的语句组合。语句被链接在一起,相互之间传递中间结果集。查询语言由多种不同的语句构成,这里是一些获取图的常用语句:
● MATCH:匹配图模式。这是从图中获取数据最常见的方法。
● WHERE:不是独立的语句,而是MATCH, OPTINAL MATCH和WITH的一部分。用于给模式添加约束或者过滤传递给WITH的中间结果。
● RETURN:定义返回的结果。
下面是MATCH和RETURN的例子。示例中的图数据如图3-1所示。
图3-1 示例中的图数据
例如,下面是查找名为’John’和’John’朋友的朋友的查询语句。
MATCH (john {name: 'John'})-[:friend]->()-[:friend]->(fof) RETURN john.name, fof.name
结果
提示
本章中这种框线输出格式是通过neo4j-shell得到的,有关neo4j-shell的使用请参见“第2章Neo4j基础入门”。
接下来在语句中添加一些过滤。
给定一个用户名列表,找到名字在列表中的所有节点。匹配他们的朋友,仅返回那些他们关注的name属性以’S’开头的用户。
MATCH (user)-[:friend]->(follower) WHERE user.name IN ['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*' RETURN user.name, follower.name
结果
下面是一些用于更新图常用的语句:
● CREATE(和DELETE):创建(和删除)节点和关系。
● SET(和REMOVE):使用SET设置属性值和给节点添加标签,使用REMOVE移除它们。
● MERGE:匹配已经存在的或者创建新节点和模式,这对于有唯一性约束的时候非常有用。
3.1.2 模式(Patterns)
Neo4j图由节点和关系构成。节点可能还有标签和属性,关系可能还有类型和属性。节点表达的是实体,关系连接一对节点。节点可以看作类似关系数据库中的表,但又不完全一样。节点的标签可以理解为不同的表名,属性类似关系数据库中表的列。一个节点的数据类似关系数据库中表的一行数据。拥有相同标签的节点通常具有类似的属性,但不必完全一样,这点与关系数据库中一张表中的行数据拥有相同的列是不一样的。
然而,节点和关系都是简单的低层次的构建块。单个节点或者关系只能编码很少的信息,但模式可以将很多节点和关系编码为任意复杂的想法。
Cypher查询语言很依赖于模式。只包含一个关系的简单模式连接了一对节点。例如,一个人LIVES_IN在某个城市或者某个城市PART_OF一个国家。使用了多个关系的复杂模式能够表达任意复杂的概念,可以支持各种有趣的使用场景。例如,下面的Cypher代码将两个简单的模式连接在一起:
(:Person) -[:LIVES_IN]-> (:City) -[:PART_OF]-> (:Country)
像关系数据库中的SQL一样,Cypher是一种文本的声明式查询语言。它使用ASCII art的形式来表达基于图的模式。采用类似SQL的语句,如MATCH、WHERE和DELETE,来组合这些模式以表达所预期的操作。
3.1.2.1 节点语法
Cypher采用一对圆括号来表示节点,如:()、(foo)。下面是一些常见的节点表示法:
() (matrix) (:Movie) (matrix:Movie) (matrix:Movie {title: "The Matrix"}) (matrix:Movie {title: "The Matrix", released: 1997})
简单的()表达了一个匿名节点。如果想在其他地方引用这个节点,可以添加一个变量,如(matrix)。此变量的可见范围局限于单个语句。
Movie标签声明了节点的类型。Neo4j节点索引也会使用到标签,每个索引都是建立在一个标签和属性的组合上。节点的属性以key/value列表的形式存在,并外加一对大括号。属性可以存储信息和(或者)限制模式。
3.1.2.2 关系语法
Cypher使用一对短横线(即--)表示一个无方向关系。有方向的关系在其中一段加上一个箭头(即<--或-->)。方括号表达式[…]可用于添加详情。里面可以包含变量、属性和(或者)类型信息。关系的常见表达方式如下:
--> -[role]-> -[:ACTED_IN]-> -[role:ACTED_IN]-> -[role:ACTED_IN {roles: ["Neo"]}]->
关系的方括号内的语法和语义与节点类似,定义了可以在别处引用的变量,关系的类型类似于节点的标签,关系的属性等同于节点的属性。注意,属性的值可以是数组。
3.1.2.3 模式语法
将节点和关系的语法组合在一起可以表达模式。下面是一个简单的模式:
(keanu:Person:Actor {name: "Keanu Reeves"})-[role:ACTED_IN {roles: ["Neo"]}]-> (matrix:Movie {title: "The Matrix"} )
3.1.2.4 模式变量
为了增强模块性和减少重复,Cypher允许将模式赋给一个变量。这使得匹配到的路径可以用于其他表达式。如:
acted_in = (:Person)-[:ACTED_IN]->(:Movie)
3.1.3 查询和更新图
Cypher语句既可用于查询,又可用于更新图数据。
3.1.3.1 更新语句的结构
一个Cypher查询部分不能同时匹配和更新图数据。每个部分要么读取和匹配图,要么更新它。
如果需要从图中读取,然后更新图,那么该查询隐含地包含两个部分——第一部分是读取,第二部分是写入。如果查询只是读取,Cypher将采用惰性加载(Lazy Load),事实上并没匹配模式,直到需要返回结果时才实际地去匹配。在更新查询语句中,所有的读取操作必须在任何的写操作发生之前完成。
当希望使用聚合数据进行过滤时,必须使用WITH将两个读语句部分连接在一起。第一部分做聚合,第二部分过滤来自第一部分的结果。如下所示:
MATCH (n {name: 'John'})-[:FRIEND]-(friend) WITH n, count(friend) AS friendsCount WHERE friendsCount > 3 RETURN n, friendsCount
下面是一个将聚合数据更新到图中的例子:
MATCH (n {name: 'John'})-[:FRIEND]-(friend) WITH n, count(friend) AS friendsCount SET n.friendCount = friendsCount RETURN n.friendsCount
在内存运行的情况下,可以尽可能多地将查询部分链接在一起。
3.1.3.2 返回数据
任何查询都可以返回数据。RETURN语句有三个子语句,分别为SKIP、LIMIT和ORDER BY。如果返回的图元素是刚刚删除的数据,需要注意的是这时数据的指针不再有效,针对它们的任何操作都是未定义的。
3.1.4 事务
任何更新图的查询都运行在一个事务中。一个更新查询要么全部成功,要么全部失败。Cypher或者创建一个新的事务,或者运行在一个已有的事务中:
● 如果运行上下文中没有事务,Cypher将创建一个,一旦查询完成就提交该事务。
● 如果运行上下文中已有事务,查询就会运行在该事务中。直到该事务成功地提交之后,数据才会持久化到磁盘中去。
可以将多个查询作为单个事务来提交:
(1)开始一个事务。
(2)运行多个Cypher更新查询。
(3)一次提交这些查询。
提示
查询将这些变化放在内存中,直到整个查询执行完成。一个巨大的查询会导致JVM使用大量的堆空间。
3.1.5 唯一性
当进行模式匹配时,Neo4j将确保单个模式中不会包含匹配到多次的同一个图关系。在大多数情况下,这是非常敏感的事。
例如:查找一个用户的朋友的朋友不应该返回该用户。
下面创建一些节点和关系:
CREATE (adam:User { name: 'Adam' }), (pernilla:User { name:'Pernilla' }),
(david:User { name: 'David'}), (adam)-[:FRIEND]->(pernilla),
(pernilla)-[:FRIEND]->(david)
上面的Cypher创建的图如图3-2所示。
图3-2 Cypher创建的图
下面来查询Adam的朋友的朋友:
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-()-[r2:FRIEND]- (friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName
结果
在这个查询中,Cypher确保不会包含关系r1和r2指向的同一个图关系。
然后,有时也未必一直希望这样。如果查询应当返回该用户,可以通过多个MATCH语句延伸匹配关系来实现,如下:
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend) MATCH (friend)-[r2:FRIEND]-(friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName
结果
注意,下面的查询看起来与前一个类似,但事实上它等价于再前一个。
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend), (friend)- [r2:FRIEND]-(friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName
这里的MATCH语句包含一个有两条路径的单个模式,而前一个查询有两个不同的模式。
3.1.6 兼容性
Cypher不是一成不变的语言。新版本引入了很多新的功能,一些旧的功能可能会被移除。如果需要,旧版本依然可以访问到。这里有两种方式在查询中选择使用哪个版本:
● 为所有查询设置版本:可以通过neo4j.conf中cypher.default_language_version参数来配置Neo4j数据库使用哪个版本的Cypher语言。
● 在查询中指定版本:简单地在查询开始的时候写上版本,如Cypher 2.3。
下面是一个指定使用Cypher 2.3的查询例子:
CYPHER 2.3 START n=node:nodes(name = "A") RETURN n
Neo4j 3.1支持如下版本的Cypher语言:
● Neo4j Cypher 3.1
● Neo4j Cypher 3.0
● Neo4j Cypher 2.3