CREATE: создаём узлы и связи
Наполняем граф с нуля: узлы, их свойства и связи между ними одной командой CREATE.
CREATE — команда Cypher, которая всегда создаёт новые узлы и/или связи по заданному паттерну, даже если похожие уже есть.
В прошлом уроке мы научились читать паттерны. Хорошая новость: для записи используется тот же самый синтаксис скобок и стрелок. Разница лишь в глаголе перед паттерном. Если поставить MATCH — паттерн будет искаться в существующих данных. Если поставить CREATE — тот же паттерн будет материализован, то есть превращён в реальные узлы и связи на диске. Это очень удобно: один раз освоив язык рисунков, вы умеете и читать, и писать граф.
Создаём узлы
Самый простой CREATE добавляет узел с меткой и свойствами:
CREATE (p:Person {name:'Алиса', born:1990})
RETURN pЗа один запрос можно создать несколько узлов через запятую:
CREATE
(alice:Person {name:'Алиса'}),
(bob:Person {name:'Боб'}),
(matrix:Movie {title:'Матрица', released:1999})Несколько слов про свойства. Их типы знакомы по любому языку: строки ('Алиса'), числа (1999), булевы (true/false), а также списки простых значений, например genres:['sci-fi','action']. Чего у свойства нет — так это вложенных объектов: значением свойства не может быть другой узел. Если хочется «вложенности», в графе это выражается не свойством, а отдельным узлом и связью к нему. Это важная смена мышления для тех, кто пришёл из мира JSON и документных баз: связь — полноценный гражданин, а не поле внутри документа.
Создаём связи
Связь создаётся тем же паттерном со стрелкой. Чтобы соединить два узла, их обычно сначала находят через MATCH, а потом создают ребро:
MATCH (a:Person {name:'Алиса'}), (b:Person {name:'Боб'})
CREATE (a)-[:ЗНАЕТ {since:2020}]->(b)Свойство {since:2020} повисло на связи — это атрибут именно знакомства. Обратите на это особое внимание: свойства живут не только на узлах, но и на рёбрах. Год знакомства, дата подписки, гонорар актёра за роль, вес ребра в графе дорог — всё это естественно ложится на связь, а не на один из узлов. В реляционной модели для таких атрибутов пришлось бы заводить отдельную таблицу-связку со своими колонками; в графе они просто крепятся к ребру.
Можно создать узлы и связь сразу в одном CREATE, если узлов ещё нет:
CREATE (a:Person {name:'Вера'})-[:РАБОТАЕТ_В]->(c:Company {name:'CodeChick'})Здесь одной командой родились два узла и связь между ними. Удобно для первичного наполнения базы, когда вы точно знаете, что таких сущностей ещё нет. А вот для дозаписи в уже населённую базу этот приём опасен — почему, разберём прямо сейчас.
Опасность дублей
Главное, что нужно усвоить про CREATE: он не проверяет существование. Если выполнить такой запрос дважды:
CREATE (p:Person {name:'Алиса'})в базе окажутся две Алисы — два разных узла с одинаковыми свойствами. Для данных, которые должны быть уникальны (пользователь, товар), это ловушка. Решение — MERGE из следующего урока и ограничения уникальности из раздела про индексы.
Почему так задумано, а не «исправлено»? Потому что у CREATE есть законные применения, где дубли — это норма. Каждое событие, лог, транзакция, лайк — отдельная сущность, даже если у двух лайков совпали все свойства. CREATE честно говорит: «я создаю новый факт». Ответственность за уникальность там, где она нужна, лежит на вас: либо вы используете MERGE (поиск-или-создание), либо ставите ограничение уникальности на ключевое свойство, и тогда повторный CREATE упадёт с ошибкой вместо тихого дубля. Безопасный по умолчанию вариант для справочных сущностей — именно MERGE.
Как работает под капотом
CREATE — это чистая запись: движок выделяет новые записи узлов/связей в store-файлах, проставляет свойства и связывает рёбра в списки смежности. Никаких проверок и поисков существующего он не делает, поэтому CREATE — самая быстрая операция вставки. Вся работа происходит в транзакции: либо весь CREATE применился, либо ничего (об ACID — отдельный урок).
Чуть подробнее про «связывает рёбра в списки смежности». Когда создаётся связь между Алисой и Бобом, движок не просто кладёт где-то запись «ребро #42». Он дописывает ссылку на это ребро в список связей обоих узлов — и Алисы, и Боба. Благодаря этому позже, при чтении, чтобы получить соседей узла, не нужно сканировать всю базу: достаточно пройти по короткому списку, физически прикреплённому к узлу. Цена этого — запись связи чуть дороже, чем «просто строчка в таблице»: обновляются структуры у двух узлов. Но это разовая плата при вставке, окупающаяся быстрыми обходами при каждом чтении.
Полезно помнить и про порядок частей в одном CREATE: переменные, объявленные раньше в команде, видны дальше. Поэтому CREATE (a:Person {name:'Вера'})-[:РАБОТАЕТ_В]->(c:Company) работает — к моменту создания связи a уже определена. А вот сослаться в CREATE на узел, которого нет ни в базе, ни выше в этой же команде, нельзя: его сперва надо либо найти через MATCH, либо создать.
Частые ошибки
- CREATE вместо MERGE для уникальных сущностей. Повторный запуск наплодит дубли. Для «создать, если нет» нужен MERGE.
- Создавать связь, не найдя узлы. Если узлов ещё нет,
MATCH ... CREATEничего не свяжет (MATCH вернёт пусто). Тогда либо создавайте узлы в том же CREATE, либо используйте MERGE. - Забыть RETURN при отладке. Без
RETURNзапрос отработает, но вы не увидите, что именно создалось.
Итоги
CREATEвсегда добавляет новые узлы/связи по паттерну, без проверки на существование.- Несколько узлов и связей создаются за один запрос через запятую.
- Чтобы связать существующие узлы — сперва
MATCH, затемCREATEребра. - Для уникальных данных CREATE опасен дублями — используйте MERGE и ограничения.