引言
在本教程中,将学习如何在 Cypher 查询中有效地使用逻辑运算符,并掌握在图数据库中创建、检索和修改关系的基本技能。
关系是图数据库强大之处的核心,虽然节点存储着实体,但正是它们之间的关系才真正释放了像 Neo4j 这样的图数据库的价值所在。
完成本教程后,你将能够熟练掌握如何操作关系,并结合逻辑运算符创建高效的查询。
初始化数据库
在深入操作符和关系之前,先创建一个示例数据库来使用,构建一个简单的社交网络,包含用户、他们的兴趣以及他们之间的各种联系。
创建 Person 节点
1
2
3
4
5
6
| CREATE (john:Person {name: "John", age: 28, city: "New York"})
CREATE (sarah:Person {name: "Sarah", age: 26, city: "Boston"})
CREATE (mike:Person {name: "Mike", age: 32, city: "Chicago"})
CREATE (emily:Person {name: "Emily", age: 24, city: "New York"})
CREATE (david:Person {name: "David", age: 35, city: "San Francisco"})
CREATE (laura:Person {name: "Laura", age: 29, city: "Chicago"})
|
创建 Interest 节点
1
2
3
4
5
| CREATE (music:Interest {name: "Music", type: "Art"})
CREATE (coding:Interest {name: "Coding", type: "Technology"})
CREATE (hiking:Interest {name: "Hiking", type: "Outdoor"})
CREATE (photography:Interest {name: "Photography", type: "Art"})
CREATE (cooking:Interest {name: "Cooking", type: "Culinary"})
|
Cypher 中的逻辑运算符
我们先来了解如何在 Cypher 查询中使用逻辑运算符 IN、AND 和 OR。这些运算符可以帮助我们根据多个条件筛选结果。
IN 运算符
IN 运算符允许您检查属性值是否在指定的值列表中,它是多个 OR 条件的简写形式。
1
2
3
| MATCH (p:Person)
WHERE p.city IN ["New York", "Chicago"]
RETURN p.name, p.city
|
执行结果:
1
2
3
4
5
6
| | p.name | p.city |
|---------|-----------|
| "John" | "New York"|
| "Emily" | "New York"|
| "Mike" | "Chicago" |
| "Laura" | "Chicago" |
|
AND 运算符
AND 运算符用于组合多个条件,只有当所有条件都为真时,记录才会被包含在结果中。
1
2
3
| MATCH (p:Person)
WHERE p.age > 25 AND p.city = "Chicago"
RETURN p.name, p.age, p.city
|
执行结果:
1
2
3
4
| | p.name | p.age | p.city |
|---------|-------|-----------|
| "Mike" | 32 | "Chicago" |
| "Laura" | 29 | "Chicago" |
|
OR 运算符
OR 运算符用于包含至少满足其中一个条件的记录。
1
2
3
| MATCH (p:Person)
WHERE p.age < 25 OR p.age > 30
RETURN p.name, p.age
|
执行结果:
1
2
3
4
5
| | p.name | p.age |
|---------|-------|
| "Mike" | 32 |
| "Emily" | 24 |
| "David" | 35 |
|
组合多个运算符
将这些运算符组合起来创建更复杂的查询,使用括号来控制计算顺序。
1
2
3
| MATCH (p:Person)
WHERE p.city = "New York" AND (p.age < 25 OR p.age > 30)
RETURN p.name, p.age, p.city
|
执行结果:
1
2
3
| | p.name | p.age | p.city |
|---------|-------|-----------|
| "Emily" | 24 | "New York"|
|
创建关系
现在我们来看看如何在图中创建节点之间的关系。Neo4j 中的关系是单向的,具有类型,并且可以包含属性。
Neo4j 存储的关系底层是「有向(单向)」,但查询层面可以自由无视方向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| MATCH (john:Person {name: "John"}), (sarah:Person {name: "Sarah"})
CREATE (john)-[:FRIENDS_WITH {since: 2019}]->(sarah)
MATCH (mike:Person {name: "Mike"}), (john:Person {name: "John"})
CREATE (mike)-[:FRIENDS_WITH {since: 2018}]->(john)
MATCH (emily:Person {name: "Emily"}), (sarah:Person {name: "Sarah"})
CREATE (emily)-[:FRIENDS_WITH {since: 2020}]->(sarah)
MATCH (david:Person {name: "David"}), (mike:Person {name: "Mike"})
CREATE (david)-[:FRIENDS_WITH {since: 2017}]->(mike)
MATCH (laura:Person {name: "Laura"}), (david:Person {name: "David"})
CREATE (laura)-[:FRIENDS_WITH {since: 2021}]->(david)
|
一次创建多条关系
你可以在一个查询中创建多个关系,这比逐一创建更高效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| MATCH (john:Person {name: "John"}), (music:Interest {name: "Music"}),
(hiking:Interest {name: "Hiking"})
CREATE (john)-[:INTERESTED_IN {level: "high"}]->(music),
(john)-[:INTERESTED_IN {level: "medium"}]->(hiking)
MATCH (sarah:Person {name: "Sarah"}), (photography:Interest {name: "Photography"}),
(cooking:Interest {name: "Cooking"})
CREATE (sarah)-[:INTERESTED_IN {level: "high"}]->(photography),
(sarah)-[:INTERESTED_IN {level: "high"}]->(cooking)
MATCH (mike:Person {name: "Mike"}), (coding:Interest {name: "Coding"}),
(hiking:Interest {name: "Hiking"})
CREATE (mike)-[:INTERESTED_IN {level: "high"}]->(coding),
(mike)-[:INTERESTED_IN {level: "medium"}]->(hiking)
MATCH (emily:Person {name: "Emily"}), (music:Interest {name: "Music"}),
(photography:Interest {name: "Photography"})
CREATE (emily)-[:INTERESTED_IN {level: "medium"}]->(music),
(emily)-[:INTERESTED_IN {level: "high"}]->(photography)
|
创建双向关系
在某些情况下,若希望建立双向关系。这可以通过两条 CREATE 语句来实现。
1
2
| MATCH (david:Person {name: "David"}), (laura:Person {name: "Laura"})
CREATE (david)-[:FRIENDS_WITH {since: 2021}]->(laura)
|
查询关系
已经建立了关系,接下来就来探索获取和查询这些关系的不同方法。
基础
1
2
| MATCH (p1:Person)-[r:FRIENDS_WITH]->(p2:Person)
RETURN p1.name, p2.name, r.since
|
通过属性过滤关系
1
2
3
| MATCH (p1:Person)-[r:FRIENDS_WITH]->(p2:Person)
WHERE r.since >= 2020
RETURN p1.name, p2.name, r.since
|
检索特定的关系
1
2
3
4
5
| MATCH (john:Person {name: "John"})-[:FRIENDS_WITH]->(friend)
RETURN friend.name
MATCH (p:Person)-[:INTERESTED_IN]->(:Interest {name: "Hiking"})
RETURN p.name
|
使用关系的逻辑运算符
1
2
3
4
| MATCH (p:Person)
WHERE (p)-[:INTERESTED_IN]->(:Interest {name: "Music"})
AND (p)-[:INTERESTED_IN]->(:Interest {name: "Photography"})
RETURN p.name
|
使用 IN 与关系类型
1
2
| MATCH (p:Person)-[r:INTERESTED_IN|CREATED|CONTRIBUTED_TO]->(i:Interest)
RETURN p.name, type(r), i.name
|
检索具有可变长度关系的路径
1
2
| MATCH (john:Person {name: "John"})-[:FRIENDS_WITH*2]->(fof)
RETURN john.name, fof.name
|
修改关系
关系创建后,后期可进行修改,让我们看看如何更新关系属性和更改关系结构。
更新关系属性
1
2
3
4
5
6
7
| MATCH (john:Person {name: "John"})-[r:FRIENDS_WITH]->(sarah:Person {name: "Sarah"})
SET r.since = 2018
RETURN john.name, sarah.name, r.since
MATCH (p:Person)-[r:INTERESTED_IN]->(i:Interest)
SET r.updated = date()
RETURN p.name, i.name, r.level, r.updated
|
删除关系
1
2
3
4
5
| MATCH (mike:Person {name: "Mike"})-[r:FRIENDS_WITH]->(john:Person {name: "John"})
DELETE r
MATCH (:Person)-[r:INTERESTED_IN {level: "high"}]->(:Interest)
DELETE r
|
替换关系
有时你可能想用一段不同的关系来取代一段关系。
1
2
3
4
| MATCH (mike:Person {name: "Mike"})-[r:INTERESTED_IN]->(:Interest {name: "Coding"})
WHERE r.level = "high"
DELETE r
CREATE (mike)-[:EXPERT_IN {since: 2015}]->(:Interest {name: "Coding"})
|
先删除,再创建。
合并关系
MERGE 命令可以用来匹配现有的关系,或者在关系不存在的情况下创建它们。
1
2
3
4
5
| MATCH (emily:Person {name: "Emily"}), (mike:Person {name: "Mike"})
MERGE (emily)-[r:FRIENDS_WITH]->(mike)
ON CREATE SET r.since = 2022, r.through = "Work"
ON MATCH SET r.updated = date()
RETURN emily.name, mike.name, r
|
实际用例
让我们来探索一些实际的用例,这些用例结合了我们所学到的关于运算符和关系的所有知识。
推荐引擎
1
2
3
4
| MATCH (john:Person {name: "John"})-[:FRIENDS_WITH]->(friend)-[:INTERESTED_IN]->(interest)
WHERE NOT (john)-[:INTERESTED_IN]->(interest)
RETURN interest.name, count(friend) as frequency
ORDER BY frequency DESC
|
根据 John 朋友的喜好向他推荐新的兴趣爱好。
寻找相互联系
1
2
| MATCH (sarah:Person {name: "Sarah"})<-[:FRIENDS_WITH]-(mutualFriend)-[:FRIENDS_WITH]->(emily:Person {name: "Emily"})
RETURN mutualFriend.name
|
找到 Sarah 和 Emily 之间的共同朋友。
复杂模式
1
2
3
4
| MATCH (p1:Person)-[:INTERESTED_IN]->(i:Interest)<-[:INTERESTED_IN]-(p2:Person)
WHERE p1.city = p2.city AND p1.name < p2.name
AND NOT (p1)-[:FRIENDS_WITH]-(p2)
RETURN p1.name, p2.name, p1.city, collect(i.name) as sharedInterests
|
找到住在同一个城市,至少有一个共同爱好,但还不是朋友的人。
高级技巧
下面是一些在 Neo4j 中处理关系的高级技巧。
使用 FOREACH 创建条件关系
1
2
3
4
5
| MATCH (p1:Person), (p2:Person)
WHERE p1.city = "Chicago" AND p2.city = "Chicago" AND p1 <> p2
FOREACH (ignoreMe IN CASE WHEN NOT (p1)-[:FRIENDS_WITH]->(p2) THEN [1] ELSE [] END |
CREATE (p1)-[:FRIENDS_WITH {since: 2022, reason: "Same city"}]->(p2)
)
|
在所有来自芝加哥的人之间建立关系。
使用 UNWIND 创建批处理关系
1
2
3
4
| MATCH (david:Person {name: "David"})
UNWIND ["Coding", "Hiking", "Cooking"] as interestName
MATCH (interest:Interest {name: interestName})
CREATE (david)-[:INTERESTED_IN {level: "medium"}]->(interest)
|
同时创建多个兴趣关系。
关系去重
1
2
3
4
5
| MATCH (p1:Person)-[r:FRIENDS_WITH]->(p2:Person)
WITH p1, p2, COLLECT(r) as rels
WHERE SIZE(rels) > 1
UNWIND TAIL(rels) as extraRel
DELETE extraRel
|
确保不存在重复的 FRIENDS_WITH 关系。
处理关系的最佳实践
- 仔细设计关系:考虑方向性、类型和属性,以最准确地建模你的领域。
- 使用有意义的关系类型:如 FRIENDS_WITH、BELONGS_TO 或 PURCHASED 这样的名称,能清晰地传达关系的性质。
- 优化关系遍历:Neo4j 的强大之处在于高效的图关系遍历。
- 对可变长度路径要谨慎:虽然功能强大,但像 [:FRIENDS_WITH*1..5] 这样的查询在大型数据库上可能会变得代价较大。
- 用于过滤器的索引属性:对于在 WHERE 子句中经常使用的属性,应创建相应的索引。
- 考虑双向关系:对于像友谊这样的关系,如果双向遍历很常见,建议创建双向关系。
- 在查询中使用参数:避免硬编码值,使用参数以提高安全性并缓存查询计划。