Раздел 39 · Neo4j и графовые базы

Паттерны Cypher: запрос как рисунок графа

junior~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлых уроках мы писали запросы, не разбирая, как они устроены. Пора это исправить. Главная идея Cypher: запрос рисуют. Ты набираешь буквами маленький кусочек графа, а база ищет все места, где граф выглядит так же. Этот кусочек называется паттерн. Сегодня собираем паттерны из деталей.

Рисунок на салфетке

Объясни приятелю на салфетке, кого ты ищешь: «человек, который живёт в Казани». Ты нарисуешь кружок «человек», кружок «Казань» и стрелку между ними. Cypher предлагает рисовать так же, только символами клавиатуры: кружок это круглые скобки, стрелка это -->.

(p:Person)-[:LIVES_IN]->(c:City {name: 'Казань'})

Слева направо: узел p с меткой Person, связь типа LIVES_IN, узел c с меткой City и свойством name, равным 'Казань'. Стрелка смотрит от человека к городу, как и связи в графе клуба.

Узел

Узел всегда в круглых скобках. Внутри три необязательные части:

  • переменная, имя, по которому к узлу обращаются дальше в запросе: p, book, friend;
  • метки после двоеточия: :Person, :Person:Moderator;
  • свойства в фигурных скобках: {name: 'Аня'}. Это условие на равенство: подойдут только узлы с таким значением.

Все части можно опускать. () значит «любой узел», (:Book) «какой-то узел с меткой Book, имя не нужно», (b:Book {author: 'Стругацкие'}) «книга Стругацких, назовём её b»:

MATCH (b:Book {author: 'Стругацкие'})
RETURN b.title, b.year
ORDER BY b.year;
+---------------------------------------------+
| b.title                            | b.year |
+---------------------------------------------+
| "Понедельник начинается в субботу" | 1965   |
| "Пикник на обочине"                | 1972   |
+---------------------------------------------+

2 rows

Связь

Связь рисуется двумя дефисами и стрелкой, детали пишутся в квадратных скобках посередине:

  • --> любая связь в направлении стрелки;
  • -[:READ]-> связь типа READ;
  • -[r:READ]-> то же и переменная r, чтобы вернуть связь или прочитать её свойства;
  • -[r:READ {rating: 5}]-> только связи с оценкой 5.

Кто поставил книгам высшую оценку:

MATCH (p:Person)-[r:READ {rating: 5}]->(b:Book)
RETURN p.name, b.title
ORDER BY p.name, b.title;
+----------------------------------------------+
| p.name  | b.title                            |
+----------------------------------------------+
| "Аня"   | "Мастер и Маргарита"               |
| "Борис" | "Двенадцать стульев"               |
| "Вера"  | "Пикник на обочине"                |
| "Вера"  | "Понедельник начинается в субботу" |
| "Жанна" | "Обломов"                          |
| "Зоя"   | "Понедельник начинается в субботу" |
+----------------------------------------------+

6 rows

Цепочки

Узлы и связи можно чередовать сколько угодно. Каждая новая пара «связь, узел» это ещё один шаг по графу. Вот цепочка из двух связей: Дина, тот, кого она знает, и тот, кого знает он.

MATCH p = (:Person {name: 'Дина'})-[:KNOWS]->()-[:KNOWS]->()
RETURN p;
+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| p                                                                                                                                                   |
+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| (:Person {name: "Дина", born: 2001})-[:KNOWS {since: 2022}]->(:Person {name: "Егор"})-[:KNOWS {since: 2017}]->(:Person {name: "Жанна", born: 1992}) |
+-----------------------------------------------------------------------------------------------------------------------------------------------------+

1 row

Конструкция p = ... даёт имя всему найденному пути. Такая переменная хранит путь: узлы и связи по порядку. cypher-shell печатает его одной строкой, так что видно и направление каждой стрелки, и свойства.

Несколько паттернов через общую переменную

Не всякий рисунок вытягивается в одну линию. Москвичи и книги, которые они читали, это два отрезка, сходящихся в одном человеке. Пишем два паттерна через запятую и связываем их одной переменной p:

MATCH (p:Person)-[:LIVES_IN]->(:City {name: 'Москва'}),
      (p)-[:READ]->(b:Book)
RETURN p.name, b.title
ORDER BY p.name, b.title;
+----------------------------------------------+
| p.name  | b.title                            |
+----------------------------------------------+
| "Аня"   | "Мастер и Маргарита"               |
| "Аня"   | "Пикник на обочине"                |
| "Борис" | "Двенадцать стульев"               |
| "Борис" | "Мастер и Маргарита"               |
| "Глеб"  | "Мастер и Маргарита"               |
| "Глеб"  | "Понедельник начинается в субботу" |
+----------------------------------------------+

6 rows

Общая переменная работает как узелок на нитке: оба паттерна обязаны пройти через один и тот же узел. Если вместо (p) во втором паттерне написать (q), связь между частями пропадёт, и база переберёт все пары «москвич, любой читатель любой книги».

Как база ищет совпадения

Полезно представлять работу MATCH по шагам:

  1. Найти кандидатов на один из узлов паттерна, например всех участников с меткой Person.
  2. От каждого кандидата пройти по связям нужного типа и направления.
  3. Проверить условия на следующем узле: метку и свойства.
  4. Повторять, пока не кончится паттерн. Каждая полностью совпавшая цепочка становится строкой ответа.

С какого узла начать, решает планировщик запросов. На ответ это не влияет, а вот на порядок строк влияет. Поэтому, когда порядок важен, его задают явно через ORDER BY.

Попробуй сам

Песочница возвращает пути «участник поставил книге пятёрку». На картинке видно, какие книги собрали больше всего высших оценок.

Поменяй оценку на 4. Потом допиши к паттерну ещё один шаг <-[:READ]-(:Person) и посмотри, кто ещё читал те же книги.

Проверь себя

Задание

Дальше

Паттерны умеют не только искать. Тем же рисунком граф создают. В следующем уроке берём пустую базу и строим в ней свой маленький клуб через CREATE.

домашка

Домашка