В уроке про property graph мы назвали четыре блока модели: узлы, связи, метки и свойства. В Neo4j у каждого блока есть свои правила. Сколько меток может быть у узла? Можно ли дать связи два типа? Что делать со связью, у которой в жизни нет направления, вроде дружбы? Разбираем по порядку.
Метки: сколько угодно, можно ни одной
Метка похожа на бейдж на конференции. Человек может носить сразу два бейджа, «участник» и «докладчик», а может прийти вовсе без бейджа. Так же и узел: у него бывает ноль, одна или несколько меток.
Добавим в клуб модератора: она участница и модератор одновременно.
CREATE (m:Person:Moderator {name: 'Майя'})
RETURN m, labels(m);
+--------------------------------------------------------------+
| m | labels(m) |
+--------------------------------------------------------------+
| (:Person:Moderator {name: "Майя"}) | ["Person", "Moderator"] |
+--------------------------------------------------------------+
1 row
Created 1 node, set 1 property, added 2 labels
Теперь Майя находится и по метке Person, и по метке Moderator:
MATCH (n:Moderator)
RETURN n.name;
+--------+
| n.name |
+--------+
| "Майя" |
+--------+
1 row
Внутри паттерна метки можно комбинировать. Двоеточие подряд или знак & значат «и то, и другое», вертикальная черта значит «или»:
MATCH (n:City|Book)
RETURN labels(n)[0] AS label, count(*) AS total
ORDER BY label;
+----------------+
| label | total |
+----------------+
| "Book" | 5 |
| "City" | 3 |
+----------------+
2 rows
Соглашение об именах: метки пишут с большой буквы в единственном числе, как имя класса: Person, Book, City. Регистр важен, person и Person для Neo4j разные метки.
Тип связи: всегда ровно один
С типами связей строже. У каждой связи ровно один тип, и он задаётся при создании. Поменять тип у существующей связи нельзя, можно только создать новую связь и удалить старую.
Если двух людей связывает и знакомство, и работа, это две связи разных типов, а не одна связь с двумя типами. Соглашение для типов: глагол заглавными буквами через подчёркивание, KNOWS, LIVES_IN, WORKS_WITH. Так паттерн читается почти как предложение: «участник LIVES_IN город».
Тип связи и её концы можно спросить функциями type(), startNode() и endNode():
MATCH (:Person {name: 'Вера'})-[r]->()
RETURN type(r) AS type, startNode(r).name AS from, endNode(r).name AS to, endNode(r).title AS book
ORDER BY type, to, book;
+---------------------------------------------------------------------+
| type | from | to | book |
+---------------------------------------------------------------------+
| "KNOWS" | "Вера" | "Аня" | NULL |
| "LIVES_IN" | "Вера" | "Казань" | NULL |
| "READ" | "Вера" | NULL | "Пикник на обочине" |
| "READ" | "Вера" | NULL | "Понедельник начинается в субботу" |
+---------------------------------------------------------------------+
4 rows
Направление: хранится всегда, учитывается по желанию
У каждой связи в Neo4j есть направление: от начального узла к конечному. Создать связь без направления нельзя, база потребует стрелку:
CREATE (a:Person {name: 'Тест'})-[:KNOWS]-(b:Person {name: 'Тест 2'});
42I56: syntax error or access rule violation - invalid relationship direction. Only directed relationships are supported in `CREATE`. (line 1, column 33 (offset: 32))
"CREATE (a:Person {name: 'Тест'})-[:KNOWS]-(b:Person {name: 'Тест 2'})"
^
42001: syntax error or access rule violation - invalid syntax
А вот при чтении направление можно не учитывать. В паттерне есть три варианта стрелки:
-[:KNOWS]->только исходящие связи: кого знает Аня;<-[:KNOWS]-только входящие: кто знает Аню;-[:KNOWS]-без наконечника: связь в любую сторону.
MATCH (:Person {name: 'Аня'})-[:KNOWS]->(f)
RETURN f.name AS knows
ORDER BY knows;
+---------+
| knows |
+---------+
| "Борис" |
| "Глеб" |
+---------+
2 rows
MATCH (:Person {name: 'Аня'})<-[:KNOWS]-(f)
RETURN f.name AS knownBy;
+---------+
| knownBy |
+---------+
| "Вера" |
+---------+
1 row
MATCH (:Person {name: 'Аня'})-[:KNOWS]-(f)
RETURN f.name AS anyDirection
ORDER BY anyDirection;
+--------------+
| anyDirection |
+--------------+
| "Борис" |
| "Вера" |
| "Глеб" |
+--------------+
3 rows
Что делать с симметричными связями
Дружба в жизни взаимна, а связь в Neo4j всё равно направлена. Частая ошибка новичка: создать две связи, туда и обратно, «чтобы было честно». Тогда каждое знакомство хранится дважды, а запрос без стрелки находит каждого друга два раза.
Правильный путь: хранить одну связь в любом направлении и читать её без стрелки. Направление при этом можно наполнить смыслом. В клубе KNOWS идёт от того, кто пригласил, к тому, кого пригласили. Аня пригласила Бориса в 2015 году, а Вера позвала Аню в 2019-м.
Есть связи, где направление важно по существу: READ идёт от человека к книге, LIVES_IN от человека к городу. Книга не читает человека, и запрос со стрелкой в обратную сторону честно ничего не найдёт.
Попробуй сам
Запрос находит все знакомства Ани в любую сторону и возвращает сами связи. На картинке видно, какие стрелки выходят из Ани, а какая входит.
Поставь стрелку ->, потом <- и сравни картинки. Затем спроси то же для Глеба: у него и входящих, и исходящих знакомств больше одного.
Проверь себя
Задание
Дальше
Мы уже написали несколько паттернов, не разбирая их устройство. В следующем уроке посмотрим на паттерн как на рисунок: из каких частей он состоит и как Cypher находит в графе всё, что на этот рисунок похоже.
домашка