bboyjing's blog

Neo4j学习笔记五【查询语言Cypher】

Cypher语言初探

Cypher是Neo4j的查询语言,与关系性数据库的查询语言SQL是一个概念。

下面语句查询了用户John看过的电影

1
2
3
4
5
6
//使用节点id查找起始节点
start user = node(0)
//指定由起始节点、HAS_SEEN关系和目标电影节点组成的匹配方式
//一个关系链接的两个节点是典型的图形模式,它时用()-[]-()描述的
match (user)-[:HAS_SEEN]->(movie)
return movie;

执行Cypher查询

使用Neo4j Shell命令执行Cypher查询

在命令行执行neo4j-shell -path=/opt/neo4j-community-3.0.3/data/databases/graph.db。如果找不到命令的话,将Neo4j bin目录添加到环境变量或者在Neo4j安装根目录的bin文件夹下执行。另外,-path的值根据自己的安装目录指定。结果如下:
图四

使用网页管理控制台执行Cypher查询

假定我们已经学过第一章,而且也安装了Neo4j,那么在命令行执行neo4j console就可以启动管理界面。打开浏览器访问提示的url,输入查询语句后点击右侧执行按钮,即可得到查询结果。
图五

用Java代码执行Cypher查询

Neo4j核心Java API包含一个Cypher API,是得从Java代码中运行Cypher查询非常简单。以下代码用来遍历John的朋友们看过的电影。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args){
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
try(Transaction tx = graphDB.beginTx()){
//通过Cypher查询获得结果
StringBuilder sb = new StringBuilder();
sb.append("start john = node(0) ");
sb.append("match (john)-[:IS_FRIEND_OF]->(USER)-[:HAS_SEEN]->(movie) ");
sb.append("return movie;");
Result result = graphDB.execute(sb.toString());
//遍历结果
while(result.hasNext()){
//get("movie")和查询语句的return movie相匹配
Node movie = (Node) result.next().get("movie");
System.out.println(movie.getId() + " : " + movie.getProperty("name"));
}
tx.success();
}
}

Cypher基本语法

Cypher句法由四个不同的部分组成,每一部分都有一个特殊的规则:

  • start–查找图形中的起始节点
  • match–匹配图形模式,可以定位感兴趣数据的子图形
  • where–基于某些标准过滤数据
  • return–返回感兴趣的结果

匹配模式

简单示例,( )表示节点,[ ]表示关系,-表示关系的七点,->表示关系的方向

1
2
3
4
start user = node(0)
//模式指定了user节点指向movie节点的HAS_SEEN关系
match (user)-[:HAS_SEEN]->(movie)
return movie;

如果不在意关系,或者不清楚哪一个是关系的起始节点或终止节点,则可以在关系的两端都使用-链接,在这种情况下解读为任意,比如想知道两个用户是否时朋友,可以用如下模式

1
2
3
start user = node(0)
match (user)-[:IS_FRIEND_OF]-(friend)
return friend

在查询中,节点可以复用,关系也一样可以,用法稍有不同

1
2
3
start user = node(0)
match (user)-[r:HAS_SEEN]->(movie)
return movie;

上述代码给:HAS_SEEN命名为r,如果像原始的查询中没有命名r的称之为匿名关系。同样,节点也可以匿名,下例演示了从user节点返回所有的HAS_SEEN关系,而不必担心movie节点

1
2
3
4
start user = node(0)
//为:HAS_SEEN命名成r
match (user)-[r:HAS_SEEN]->()
return r;

下面来逐步完成遍历出John的朋友看过但是John没有看过的电影

1
2
3
4
5
6
7
8
9
10
11
12
//遍历出John的朋友看过,John也看过的电影
//由此可以看出Cypher查询中的多个模式使用逗号分隔,相当与关系型数据库的AND语句
start john = node(0)
match (john)-[:IS_FRIEND_OF]->()-[:HAS_SEEN]->(movie),
(john)-[:HAS_SEEN]->(movie)
return movie;
//通过where条件过滤john看过的电影,与关系型数据库的where语句类似
start john = node(0)
match (john)-[:IS_FRIEND_OF]->()-[:HAS_SEEN]->(movie)
where NOT (john)-[:HAS_SEEN]->(movie)
return movie;

建议

对Cypher查询语句的测试,建议在Shell命令行中进行。虽然在网页控制台中能更直观的看到查询结果,但是有时候不一定是真实的结果,拿遍历出John的朋友看过但是John没有看过的电影为例来说。
网页控制台执行的结果
图六
图中可以直观的看出遍历出两个节点Heat和Alien,当对结果有疑问时,最好看下Rows或者Text,能看出真实结果,或者直接在Shell命令行中查询。
Shell命令行执行的结果
图七
多遍历出一条Alien,这不是错误,因为确实是有两个人看过Alien,这才是真实的结果,通过return distinct movie可以去重,这是后面涉及的内容,暂时先了解下。

查找起始节点

通过编号查找节点

1
2
3
//关键字start之后就指定了起始节点
start john= node(0)
return john;

通过多个节点编号加载多个节点

1
2
3
4
start user = node(0,2)
match (user)-[:HAS_SEEN]->(movie)
//distinct关键字用于删除重复的结果,John和Jack都看过Fargo,如果不加distinct,Fargo将会在结果集中出现两次
return distinct movie;

使用索引查找起始节点

1
2
start john = node:users(email = "john@new.example.org")
return john;

使用基于模式的索引查找节点

1
2
3
4
//使用标签通过名字查找用户节点
match (john:USERS)
where john.name= "John Johnson"
return john;

Cypher查询中的多个起始节点

1
2
3
4
5
6
//逗号分割指定多个起始节点,node:users该语句依赖前一章创建的users索引,下一章将不再使用
start john = node:users(email = "john@new.example.org"),
jack = node:users(email = "jack@example.org")
//匹配john和jack都看过的电影
match (john)-[:HAS_SEEN]->(movie),(jack)-[:HAS_SEEN]->(movie)
return movie;

过滤数据

假定每个用户都有yearOfBirth属性,下面示例查找用户John出生于1980年后的所有朋友

1
2
3
4
start john = node:users(email = "john@new.example.org")
match (john)-[:IS_FRIEND_OF]-(friend)
where friend.yearOfBirth > 1980
return friend;

使用正则表达式过滤掉指定值,下例查找所有邮箱地址包含”gmail.com”的朋友

1
2
3
4
start john = node:users(email = "john@new.example.org")
match (john)-[:IS_FRIEND_OF]-(friend)
where friend.email =~ '.*@example.org'
return friend;

过滤具有某个属性的节点

1
2
3
4
start john = node:users(email = "john@new.example.org")
match (john)-[:IS_FRIEND_OF]-(friend)
where exists(friend.twitter)
return friend;

获得结果

返回指定属性

1
2
3
4
5
start john = node:users(email = "john@new.example.org")
match (john)-[:IS_FRIEND_OF]-(user)-[:HAS_SEEN]->(movie)
where not (john)-[:HAS_SEEN]->(movie)
//只返回属性name
return movie.name;

返回关系的属性

1
2
3
start john = node:users(email = "john@new.example.org")
match (john)-[r:HAS_SEEN]->(movie)
return r.stars;

返回路径

1
2
3
4
start john = node:users(email = "john@new.example.org")
match recPath = (john)-[:IS_FRIEND_OF]-(user)-[:HAS_SEEN]->(movie)
where not (john)-[:HAS_SEEN]->(movie)
return movie.name,recPath;

结果分页

1
2
3
4
5
6
start john = node:users(email = "john@new.example.org")
match (john)-[:IS_FRIEND_OF]-(user)-[:HAS_SEEN]->(movie)
return movie
order by movie.name
skip 0
limit 1;