Недавно я читал пост Михала Бахмана о двунаправленных отношениях в Neo4j, в котором он предполагает, что для некоторых типов отношений мы не так заинтересованы в направлении отношений и поэтому можем игнорировать его при запросах. Он использует следующий пример, демонстрирующий партнерство между Neo Technology и GraphAware:
Обе компании являются партнерами друг с другом, но, поскольку мы можем так же быстро находить входящие и исходящие отношения, у нас также может быть только одно отношение между двумя компаниями / узлами.
Этот шаблон часто встречается, когда мы хотим сделать явные отношения в нашем графе явными. Например, у нас может быть следующий график, который описывает людей и проекты, над которыми они работали:
Мы могли бы создать этот граф в Neo4j 2.0, используя следующий синтаксис:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
CREATE (mark:Person {name: "Mark" }) CREATE (dave:Person {name: "Dave" }) CREATE (john:Person {name: "John" }) CREATE (projectA:Project {name: "Project A" }) CREATE (projectB:Project {name: "Project B" }) CREATE (projectC:Project {name: "Project C" }) CREATE (mark)-[:WORKED_ON]->(projectA) CREATE (mark)-[:WORKED_ON]->(projectB) CREATE (dave)-[:WORKED_ON]->(projectA) CREATE (dave)-[:WORKED_ON]->(projectC) CREATE (john)-[:WORKED_ON]->(projectC) CREATE (john)-[:WORKED_ON]->(projectB) |
Если бы мы хотели выяснить, какие люди знают друг друга, мы могли бы написать следующий запрос:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
MATCH (person1:Person)-[:WORKED_ON]-()<-[:WORKED_ON]-(person2) RETURN person1, person2 ==> +-------------------------------------------------------+ ==> | person1 | person2 | ==> +-------------------------------------------------------+ ==> | Node[500363]{name: "Mark" } | Node[500364]{name: "Dave" } | ==> | Node[500363]{name: "Mark" } | Node[500365]{name: "John" } | ==> | Node[500364]{name: "Dave" } | Node[500363]{name: "Mark" } | ==> | Node[500364]{name: "Dave" } | Node[500365]{name: "John" } | ==> | Node[500365]{name: "John" } | Node[500364]{name: "Dave" } | ==> | Node[500365]{name: "John" } | Node[500363]{name: "Mark" } | ==> +-------------------------------------------------------+ ==> 6 rows |
Возможно, мы захотим создать отношения ЗНАНИЯ между каждой парой людей:
1
2
3
|
MATCH (person1:Person)-[:WORKED_ON]-()<-[:WORKED_ON]-(person2) CREATE UNIQUE (person1)-[:KNOWS]->(person2) RETURN person1, person2 |
Теперь, если мы выполним запрос (который игнорирует направление отношений), чтобы выяснить, какие люди знают друг друга, мы получим много дублирующих результатов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
MATCH path=(person1:Person)-[:KNOWS]-(person2) RETURN person1, person2, path ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | person1 | person2 | path | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | Node[500363]{name: "Mark" } | Node[500364]{name: "Dave" } | [Node[500363]{name: "Mark" },:KNOWS[528536]{},Node[500364]{name: "Dave" }] | ==> | Node[500363]{name: "Mark" } | Node[500365]{name: "John" } | [Node[500363]{name: "Mark" },:KNOWS[528537]{},Node[500365]{name: "John" }] | ==> | Node[500363]{name: "Mark" } | Node[500364]{name: "Dave" } | [Node[500363]{name: "Mark" },:KNOWS[528538]{},Node[500364]{name: "Dave" }] | ==> | Node[500363]{name: "Mark" } | Node[500365]{name: "John" } | [Node[500363]{name: "Mark" },:KNOWS[528541]{},Node[500365]{name: "John" }] | ==> | Node[500364]{name: "Dave" } | Node[500363]{name: "Mark" } | [Node[500364]{name: "Dave" },:KNOWS[528538]{},Node[500363]{name: "Mark" }] | ==> | Node[500364]{name: "Dave" } | Node[500365]{name: "John" } | [Node[500364]{name: "Dave" },:KNOWS[528539]{},Node[500365]{name: "John" }] | ==> | Node[500364]{name: "Dave" } | Node[500363]{name: "Mark" } | [Node[500364]{name: "Dave" },:KNOWS[528536]{},Node[500363]{name: "Mark" }] | ==> | Node[500364]{name: "Dave" } | Node[500365]{name: "John" } | [Node[500364]{name: "Dave" },:KNOWS[528540]{},Node[500365]{name: "John" }] | ==> | Node[500365]{name: "John" } | Node[500364]{name: "Dave" } | [Node[500365]{name: "John" },:KNOWS[528540]{},Node[500364]{name: "Dave" }] | ==> | Node[500365]{name: "John" } | Node[500363]{name: "Mark" } | [Node[500365]{name: "John" },:KNOWS[528541]{},Node[500363]{name: "Mark" }] | ==> | Node[500365]{name: "John" } | Node[500363]{name: "Mark" } | [Node[500365]{name: "John" },:KNOWS[528537]{},Node[500363]{name: "Mark" }] | ==> | Node[500365]{name: "John" } | Node[500364]{name: "Dave" } | [Node[500365]{name: "John" },:KNOWS[528539]{},Node[500364]{name: "Dave" }] | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> 12 rows |
Каждая пара людей появляется 4 раза, и если мы возьмем пример Марка и Дейва, мы увидим, почему:
01
02
03
04
05
06
07
08
09
10
11
|
MATCH path=(person1:Person)-[:KNOWS]-(person2) WHERE person1.name = "Mark" AND person2.name = "Dave" RETURN person1, person2, path ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | person1 | person2 | path | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | Node[500363]{name: "Mark" } | Node[500364]{name: "Dave" } | [Node[500363]{name: "Mark" },:KNOWS[528536]{},Node[500364]{name: "Dave" }] | ==> | Node[500363]{name: "Mark" } | Node[500364]{name: "Dave" } | [Node[500363]{name: "Mark" },:KNOWS[528538]{},Node[500364]{name: "Dave" }] | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> 2 rows |
Если мы посмотрим под столбцом пути, между Марком и Дейвом будут две разные взаимосвязи KNOWS (с идентификаторами 528536 и 528538), одна из которых идет от Марка к Дейву, а другая от Дэйва к Марку.
Как отметил Михал в своем посте, в этом случае нет необходимости в двух отношениях. Нам нужны отношения только одним способом, который мы можем сделать, не указывая направление при создании отношения KNOWS :
1
2
3
|
MATCH (person1:Person)-[:WORKED_ON]-()<-[:WORKED_ON]-(person2) CREATE UNIQUE (person1)-[:KNOWS]-(person2) RETURN person1, person2 |
Теперь, если мы повторно запустим запрос, чтобы проверить отношения между Марком и Дейвом, есть только один:
1
2
3
4
5
6
7
8
|
MATCH path=(person1:Person)-[:KNOWS]-(person2) WHERE person1.name = "Mark" AND person2.name = "Dave" RETURN person1, person2, path ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | person1 | person2 | path | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | Node[500375]{name: "Mark" } | Node[500376]{name: "Dave" } | [Node[500375]{name: "Mark" },:KNOWS[528560]{},Node[500376]{name: "Dave" }] | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> 1 row |
В этом случае отношение переходит от Марка к Дейву, что мы можем увидеть, выполнив несколько запросов, которые учитывают направление:
01
02
03
04
05
06
07
08
09
10
|
MATCH path=(person1:Person)-[:KNOWS]->(person2) WHERE person1.name = "Mark" AND person2.name = "Dave" RETURN person1, person2, path ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | person1 | person2 | path | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> | Node[500375]{name: "Mark" } | Node[500376]{name: "Dave" } | [Node[500375]{name: "Mark" },:KNOWS[528560]{},Node[500376]{name: "Dave" }] | ==> +--------------------------------------------------------------------------------------------------------------------------------+ ==> 1 row |
1
2
3
4
5
6
7
8
9
|
MATCH path=(person1:Person)<-[:KNOWS]-(person2) WHERE person1.name = "Mark" AND person2.name = "Dave" RETURN person1, person2, path ==> +--------------------------+ ==> | person1 | person2 | path | ==> +--------------------------+ ==> +--------------------------+ ==> 0 row |