Статьи

Neo4j: Делаем неявные отношения явными и двунаправленными

Недавно я читал пост Михала Бахмана о двунаправленных отношениях в Neo4j, в котором он предполагает, что для некоторых типов отношений мы не так заинтересованы в направлении отношений и поэтому можем игнорировать его при запросах. Он использует следующий пример, демонстрирующий партнерство между Neo Technology и GraphAware:

neo_ga3

Обе компании являются партнерами друг с другом, но, поскольку мы можем так же быстро находить входящие и исходящие отношения, у нас также может быть только одно отношение между двумя компаниями / узлами.

Этот шаблон часто встречается, когда мы хотим сделать явные отношения в нашем графе явными. Например, у нас может быть следующий график, который описывает людей и проекты, над которыми они работали:

2013-10-25_16-34-16

Мы могли бы создать этот граф в 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