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

Граф как модель данных: property graph

junior~15 мин

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

В прошлом уроке мы говорили о графах на бытовом уровне: «узлы», «связи», «у связи есть свойство». Пора договориться о точных словах. Neo4j хранит данные в модели, которая называется property graph. В ней всего четыре строительных блока, и все они уже есть в учебном графе песочницы.

Доска с карточками и нитками

Вспомни детективный сериал: на стене висят фотографии подозреваемых, между ними натянуты красные нитки, на каждой нитке бумажка «звонил 12 мая». Это и есть property graph:

  • фотография это узел;
  • подпись над фотографией «свидетель» или «подозреваемый» это метка;
  • нитка это связь, у неё есть тип («звонил») и направление (кто кому);
  • записи на обороте фотографии и на бумажке у нитки это свойства.

Теперь то же самое строго.

Узлы и метки

Узел это объект: человек, книга, город. Метка говорит, к какой группе узел относится. В учебном графе три метки: Person, City и Book. Посчитаем узлы по меткам:

MATCH (n)
RETURN labels(n) AS labels, count(*) AS total
ORDER BY total DESC;
+--------------------+
| labels     | total |
+--------------------+
| ["Person"] | 10    |
| ["Book"]   | 5     |
| ["City"]   | 3     |
+--------------------+

3 rows

Функция labels(n) возвращает список меток узла: у одного узла их может быть несколько, например Person и Admin сразу. Метка похожа на имя таблицы, но только похожа: узел не обязан иметь все те же поля, что его соседи по метке.

Связи, тип и направление

Связь соединяет ровно два узла. У неё всегда есть начало и конец, то есть направление, и ровно один тип. В клубе три типа связей:

  • KNOWS: участник знает другого участника;
  • LIVES_IN: участник живёт в городе;
  • READ: участник прочитал книгу.
MATCH ()-[r]->()
RETURN type(r) AS type, count(r) AS total
ORDER BY type;
+--------------------+
| type       | total |
+--------------------+
| "KNOWS"    | 10    |
| "LIVES_IN" | 8     |
| "READ"     | 15    |
+--------------------+

3 rows

Связь в графе не бывает «висячей»: нельзя создать связь без двух концов. И нельзя удалить узел, пока на нём держатся связи, база не даст. Направление обязательно при записи, но при чтении его можно не учитывать, об этом урок про метки, типы и направление.

Свойства

Свойство это пара «ключ, значение». Свойства бывают и у узлов, и у связей. У Ани есть name и born, у связи KNOWS есть since, год знакомства, у READ есть rating, оценка книги. Значением может быть целое или дробное число, строка, логическое значение, дата или список из значений одного типа. Вложенных объектов в свойствах нет: карта целиком в свойство не положится.

Посмотрим на всё сразу: Аню и всё, на что от неё указывают стрелки.

MATCH (a:Person {name: 'Аня'})-[r]->(x)
RETURN a, r, x;
+---------------------------------------------------------------------------------------------------------------------------------------+
| a                                   | r                      | x                                                                      |
+---------------------------------------------------------------------------------------------------------------------------------------+
| (:Person {name: "Аня", born: 1994}) | [:READ {rating: 4}]    | (:Book {title: "Пикник на обочине", year: 1972, author: "Стругацкие"}) |
| (:Person {name: "Аня", born: 1994}) | [:READ {rating: 5}]    | (:Book {title: "Мастер и Маргарита", year: 1967, author: "Булгаков"})  |
| (:Person {name: "Аня", born: 1994}) | [:LIVES_IN]            | (:City {name: "Москва"})                                               |
| (:Person {name: "Аня", born: 1994}) | [:KNOWS {since: 2020}] | (:Person {name: "Глеб", born: 1987})                                   |
| (:Person {name: "Аня", born: 1994}) | [:KNOWS {since: 2015}] | (:Person {name: "Борис", born: 1990})                                  |
+---------------------------------------------------------------------------------------------------------------------------------------+

5 rows

Так cypher-shell печатает графовые значения: узел в круглых скобках с метками через двоеточие и свойствами в фигурных, связь в квадратных скобках с типом. Аня повторяется в каждой строке, потому что каждая строка ответа это одна найденная связь.

Чем это отличается от таблиц

Реляционная модельProperty graph
Строка таблицыУзел
Имя таблицыМетка
КолонкаСвойство
Внешний ключ или связующая таблицаСвязь
Колонка связующей таблицыСвойство связи
Схема обязательнаСхема по желанию

Главная разница в последних двух строках. Связь в графе это полноправная запись со своими свойствами, а не пара номеров в отдельной таблице. А схема необязательна: у Егора в клубе нет года рождения, и базе это не мешает, она не хранит пустую колонку. Свобода работает в обе стороны: опечатку в имени свойства база тоже не заметит. Как защищаться от этого, расскажем в уроке про индексы и ограничения.

Есть и другая графовая модель, RDF, где всё записывается тройками «субъект, предикат, объект». У связи в RDF нет своих свойств, и год знакомства пришлось бы выражать обходными путями. Neo4j работает с property graph, и дальше мы говорим только о нём.

Попробуй сам

Запрос из урока в песочнице: Аня и всё, на что она указывает. Картинка под ответом покажет книги, город и знакомых вокруг неё.

Поменяй стрелку -> на <- и посмотри, кто указывает на Аню. Потом вместо Ани возьми 'Яна': почему ответ пустой?

Проверь себя

Задание

Дальше

Модель понятна, пора поставить настоящую базу. В следующем уроке разберём, что такое Neo4j, какие у неё редакции и как запустить её в Docker за пару минут.

домашка

Домашка