Cypher против SQL: те же задачи, разный путь
Ставим SQL и Cypher рядом на одних задачах — чтобы увидеть, где граф реально упрощает, а где нет.
Cypher и SQL решают пересекающиеся задачи, но Cypher выражает связи как паттерны, тогда как SQL выражает их как соединения таблиц по ключам.
Лучший способ понять, что граф даёт на самом деле, — поставить два языка рядом на одной и той же задаче. Тогда видно, где разница чисто косметическая (Cypher просто короче), а где принципиальная (граф меняет саму стоимость операции). Этот урок намеренно честный: мы покажем и где граф побеждает с разгромным счётом, и где он, наоборот, проигрывает зрелому SQL. Без этого баланса вы рискуете тащить граф туда, где он только мешает.
Друзья друзей
На SQL — два самосоединения таблицы дружбы:
CREATE TABLE friendship (a_id INTEGER, b_id INTEGER);
INSERT INTO friendship VALUES (1,2),(2,3),(2,4);
SELECT DISTINCT f2.b_id AS fof
FROM friendship f1
JOIN friendship f2 ON f1.b_id = f2.a_id
WHERE f1.a_id = 1 AND f2.b_id <> 1;Вывод:
fof 3 4
То же на Cypher — паттерн, повторяющий рисунок:
MATCH (me:Person {id:1})-[:ЗНАЕТ]->()-[:ЗНАЕТ]->(fof)
WHERE fof.id <> 1
RETURN DISTINCT fof.idДва шага дружбы — две стрелки. Где SQL прячет связь в условии JOIN, Cypher рисует её явно. Заметьте: пример с таблицей помечен language-sql и реально исполняется в SQLite-песочнице — можете запустить и убедиться, что вернутся именно 3 и 4 (друзья друга №2). Cypher же помечен language-text: для него нужен живой Neo4j, в браузере он не выполнится. Читая эти два блока, вы видите ровно одну и ту же логику двумя глазами — реляционным и графовым.
Почему JOIN читается труднее обхода
В SQL-версии связь «дружбы» нигде не названа явно — она спрятана в условии ON f1.b_id = f2.a_id. Чтобы понять запрос, читателю надо в голове восстановить: «ага, тут стыкуются конец одной дружбы и начало другой». В Cypher та же мысль нарисована стрелками: (me)-[:ЗНАЕТ]->()-[:ЗНАЕТ]->(fof) буквально читается как «я знаю кого-то, кто знает кого-то». На двух уровнях разница в читаемости приятная, но не решающая. Решающей она становится с глубиной.
Глубина решает всё
Разница незаметна на двух уровнях, но взрывается на пяти. «Связаны ли два человека через до 5 рукопожатий» на SQL — это пять самосоединений (или рекурсивный CTE), а на Cypher — -[:ЗНАЕТ*1..5]-. И дело не только в краткости: реляционный план соединяет всю таблицу на каждом уровне, а граф идёт по смежности.
| Задача | SQL | Cypher |
| 1 уровень связи | 1 JOIN — легко | 1 стрелка |
| 5 уровней | 5 JOIN или рекурсивный CTE | *1..5 |
| неизвестная глубина | рекурсивный CTE, громоздко | * с границей |
| кратчайший путь | вручную/расширения | shortestPath |
| агрегат по колонке | прост и эффективен | возможно, но не сильная сторона |
Где SQL уместнее
Честность важнее хайпа. Табличная аналитика, строгие схемы, массовые агрегаты по колонкам, транзакционная нагрузка с простыми связями — здесь реляционная база зрелее, эффективнее и дешевле в эксплуатации. Граф не отменяет SQL; он закрывает класс задач со связями, который SQL даётся тяжело.
Конкретный пример, где SQL уверенно лучше: «выручка по месяцам за год». Это группировка с суммой по колонке — реляционный движок делает это идеально, опираясь на колоночные оптимизации и десятилетия настройки. На графе тот же отчёт пришлось бы выражать неуклюже, перебирая узлы-заказы. Другой пример — строгие ограничения целостности (внешние ключи с каскадами, CHECK-условия, типы колонок): в реляционном мире это зрелый, декларативный инструментарий. Граф гибче в схеме, но эту гибкость вы оплачиваете тем, что часть проверок переезжает в приложение. Поэтому зрелая система чаще всего гибридна: учёт и отчётность — в SQL, связи и обходы — в графе.
Как работает под капотом
Корень различия — как хранится связь. В реляционной модели связь — это совпадение значений (внешний ключ), и чтобы её пройти, нужен поиск совпадений (JOIN), стоящий, грубо, как обход индекса или таблицы. В графе связь — физический указатель, и переход по ней — это разыменование. Поэтому SQL платит за каждый уровень связи поиском, а граф — лишь шагом по указателю. На мелкой глубине поиск дёшев; на большой — выигрывает указатель.
Прикинем стоимость на пальцах. Пусть в таблице дружбы миллион строк, и стоит индекс. Один JOIN — это для каждой строки слева поиск совпадений в индексе, скажем, по log(n) операций. На двух уровнях это терпимо. Но на пяти уровнях промежуточные результаты раздуваются, и каждый новый JOIN снова и снова просеивает индекс по растущему набору ключей. В графе же «друг» — это готовый указатель в записи связи: переход по нему стоит примерно как разыменование указателя в программе, и эта стоимость не зависит от размера всей базы — только от числа соседей конкретного узла. Это свойство называют «index-free adjacency»: смежность хранится прямо при узле, без обращения к глобальному индексу. Именно оно делает глубокие обходы в графе дешёвыми там, где SQL захлёбывается.
Частые ошибки
- «Граф всегда лучше SQL». Нет — на агрегатах и простых выборках SQL часто выигрывает.
- Тащить рекурсивные CTE туда, где нужен граф. Глубокие иерархии на SQL пишутся и работают тяжело — это сигнал к графу.
- Сравнивать только синтаксис. Главная разница не в краткости, а в стоимости обхода на глубине.
Итоги
- Cypher выражает связи паттернами, SQL — соединениями; на 1 уровне разница мала.
- На глубоких обходах граф резко выигрывает: указатель против поиска совпадений.
- SQL остаётся сильнее на агрегатах, строгих схемах и табличной аналитике.
- Выбор — по форме задачи: связи → граф, таблицы и агрегаты → SQL.