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

DELETE и DETACH DELETE: удаляем безопасно

junior~20 мин

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

В прошлом уроке мы правили содержимое узлов. Осталось научиться их удалять. Здесь у графа есть защитное правило, которого нет у таблиц: узел нельзя удалить, пока на нём держатся связи. Разбираемся, зачем оно нужно и как удалять так, чтобы не пожалеть.

Сначала посмотри, потом удаляй

Хороший столяр меряет семь раз. В Cypher «померить» значит выполнить тот же MATCH с RETURN и посмотреть, что найдётся. Только убедившись, что в ответе ровно то, что нужно стереть, меняешь RETURN на DELETE. Удаление в Neo4j не спрашивает подтверждения, и корзины, откуда можно достать удалённое, нет.

Удалить связь

Аня и Глеб поссорились, знакомство нужно убрать. Сначала смотрим:

MATCH (:Person {name: 'Аня'})-[r:KNOWS]->(:Person {name: 'Глеб'})
RETURN r;
+------------------------+
| r                      |
+------------------------+
| [:KNOWS {since: 2020}] |
+------------------------+

1 row

Связь одна, та самая. Удаляем:

MATCH (:Person {name: 'Аня'})-[r:KNOWS]->(:Person {name: 'Глеб'})
DELETE r;
0 rows
Deleted 1 relationship

Узлы Ани и Глеба остались на месте, исчезла только связь между ними.

Удалить узел без связей

У Яны нет ни одной связи, её узел удаляется обычным DELETE:

MATCH (p:Person {name: 'Яна'})
DELETE p;
0 rows
Deleted 1 node

Узел со связями: защита от висячих связей

С Ильёй так не выйдет, у него знакомство и два отзыва:

MATCH (p:Person {name: 'Илья'})
DELETE p;
G1001: dependent object error - edges still exist

Ошибка edges still exist значит «у узла ещё есть связи». Это правило из урока про property graph: связь не может висеть без конца. Если удалить узел и оставить связи, они указывали бы в пустоту. Neo4j отказывается, и запрос не меняет ничего.

У защиты есть смысл: она ловит ошибку «удалил не тот узел». Если ты собирался удалить участника-дубликат без связей, а MATCH нашёл настоящего, ошибка спасёт его вместе со всеми знакомствами.

DETACH DELETE: вместе со связями

Если связи действительно нужно удалить вместе с узлом, пишут DETACH DELETE:

MATCH (p:Person {name: 'Илья'})
DETACH DELETE p;
0 rows
Deleted 1 node, deleted 3 relationships

Один узел и три связи: знакомство с Зоей и два отзыва на книги. Сами книги и Зоя остались.

Можно удалять и вручную, перечислив связи и узел в одном DELETE. Это то же самое, но явное:

MATCH (p:Person {name: 'Егор'})-[r]-()
DELETE r, p;
0 rows
Deleted 1 node, deleted 4 relationships

Удалить по условию

DELETE работает с каждой найденной строкой. Удалим все отзывы на «Обломова», а заодно посчитаем их:

MATCH (b:Book {title: 'Обломов'})<-[r:READ]-()
DELETE r
RETURN count(r) AS removed;
+---------+
| removed |
+---------+
| 2       |
+---------+

1 row
Deleted 2 relationships

Удалённую связь ещё можно посчитать в том же запросе, но читать её свойства после DELETE уже не стоит.

Мягкое удаление

Часто данные удалять жалко: участник ушёл из клуба, но его отзывы ещё пригодятся для рекомендаций, а историю хочется восстановить, если он вернётся. Тогда вместо DELETE узел помечают. Такой приём называют мягким удалением:

MATCH (p:Person {name: 'Зоя'})
SET p:Archived, p.archivedAt = date('2026-10-24')
RETURN p;
+----------------------------------------------------------------------+
| p                                                                    |
+----------------------------------------------------------------------+
| (:Person:Archived {name: "Зоя", archivedAt: 2026-10-24, born: 1999}) |
+----------------------------------------------------------------------+

1 row
Set 1 property, added 1 label

Связи Зои на месте, а запросы приложения пропускают архивных:

MATCH (p:Person)
WHERE NOT p:Archived
RETURN count(p) AS active;
+--------+
| active |
+--------+
| 6      |
+--------+

1 row

Вернуть участника можно одним REMOVE p:Archived. Цена приёма в том, что каждый запрос теперь должен помнить про метку, иначе архивные участники всплывут в ответах.

Стереть всё

На учебной базе иногда нужно начать с нуля. Запрос «удалить все узлы вместе со связями» короткий и страшный:

MATCH (n)
DETACH DELETE n;
0 rows
Deleted 15 nodes, deleted 23 relationships
MATCH (n)
RETURN count(n) AS nodes;
+-------+
| nodes |
+-------+
| 0     |
+-------+

1 row

В рабочей базе так не делают никогда. А на большой учебной базе такой запрос пытается удалить всё одной транзакцией и может упереться в память. Для больших объёмов удаляют порциями через CALL { ... } IN TRANSACTIONS, его мы увидим в уроке про LOAD CSV. Индексы и ограничения при этом не удаляются, их убирают отдельно, об этом следующий урок.

Попробуй сам

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

Посмотри на картинку: после удаления Егора от Дины больше нельзя дойти до второй половины клуба. Попробуй удалить Глеба обычным DELETE и прочитай ошибку.

Проверь себя

Задание

Дальше

Удаление, как и любая запись, опирается на поиск. Когда узлов миллионы, искать участника по имени перебором долго. В следующем уроке разберём индексы, которые ускоряют поиск, и ограничения, которые не пускают в базу дубли.

домашка

Домашка