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]-. И дело не только в краткости: реляционный план соединяет всю таблицу на каждом уровне, а граф идёт по смежности.

ЗадачаSQLCypher
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.
Проверьте себя
1. Почему «друзья друзей» в SQL требуют двух JOIN, а в Cypher — двух стрелок?
ACypher просто короче пишется
BКаждый уровень связи в SQL — это соединение по ключам, в графе — шаг по паттерну/указателю
CSQL не поддерживает связи
DCypher кэширует результат
2. Где реляционная база, скорее всего, эффективнее графа?
AКратчайший путь
BМассовые агрегаты по колонкам и табличная аналитика
CОбнаружение сообществ
DДрузья друзей на 6 уровней
3. В чём корневое различие хранения связи в SQL и в графе?
AВ графе связи нет
BSQL хранит связь как совпадение значений (поиск при обходе), граф — как физический указатель (разыменование)
CSQL быстрее на любой глубине
DРазницы нет