Neo4j权威指南 (图数据库技术丛书)
上QQ阅读APP看书,第一时间看更新

3.1 Cypher概述

3.1.1 Cypher是什么

Cypher是一种声明式图数据库查询语言,它具有丰富的表现力,能高效地查询和更新图数据。对于初学者Cypher使用相对简单,但其功能还是非常强大,即便是非常复杂的数据库查询也能用Cypher简要地表达出来。这使得用户可以将精力集中在自己所从事的领域,而不用在数据库访问上花太多时间。

Cypher查询语言设计很人性化,既适合开发人员,也适合专业的运营人员(这点尤为重要)。作为一个声明式查询语言,Cypher专注于清晰地表达从图中检索什么,而不是怎么去检索。在这点上,与命令式的Java语言和脚本式的Gremlinhttp://gremlin.tinkerpop.com语言完全不同。

Cypher博采众长,同时也继承了已有的惯用做法。像WHERE和ORDER BY等大多数关键词均来自于SQL语言http://en.wikipedia.org/wiki/SQL。而像模式匹配表达方法借鉴于SPARQL语言http://en.wikipedia.org/wiki/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 arthttps://en.wikipedia.org/wiki/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