MATCH и RETURN: находим и возвращаем
Базовая пара любого чтения в Cypher: найти подграф и решить, что из него вернуть.
MATCH ищет в графе подграфы заданной формы, RETURN определяет, какие узлы, связи и свойства попадут в результат.
Простейший запрос на чтение
MATCH описывает паттерн, RETURN — что отдать. Найдём все фильмы:
MATCH (m:Movie)
RETURN m.title, m.releasedЭто вернёт таблицу из двух колонок. Можно вернуть и сам узел целиком (RETURN m) — тогда Browser нарисует граф.
Сразу полезное различение, которое экономит много недоумения. RETURN m возвращает узел как объект — со всеми его свойствами, метками и внутренним id; визуально в Neo4j Browser это кружок графа. RETURN m.title возвращает одно скалярное значение — строку. Когда вам нужен табличный отчёт (выгрузить в CSV, показать в таблице), возвращайте конкретные свойства. Когда нужно визуально исследовать граф или передать узел дальше в коде драйвера — возвращайте сам узел. Новички часто пишут RETURN m, ждут аккуратную колонку с названиями, а получают объекты — теперь вы знаете почему.
Псевдонимы и сортировка
AS задаёт имя колонки, ORDER BY сортирует, LIMIT ограничивает, SKIP пропускает — всё как в SQL:
MATCH (m:Movie)
RETURN m.title AS film, m.released AS year
ORDER BY year DESC
LIMIT 5Это «пять самых свежих фильмов». Сравним с SQL — структура знакома:
SELECT title AS film, released AS year
FROM movie
ORDER BY year DESC
LIMIT 5;Парность очевидна: RETURN ≈ SELECT, MATCH (m:Movie) ≈ FROM movie, а AS, ORDER BY, LIMIT работают один в один. SKIP n пропускает первые n строк — связка SKIP + LIMIT даёт постраничную выдачу (пагинацию): SKIP 20 LIMIT 10 — это «третья страница по десять элементов». Этой части языка вообще не нужно учиться заново, если вы знаете SQL: переносите привычки как есть.
Возврат по связям
Главная разница с SQL начинается на связях. «Кто снимался в Матрице»:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie {title:'The Matrix'})
RETURN p.name AS actor
ORDER BY actorНикакого JOIN — просто паттерн со стрелкой. Вдумайтесь, насколько это короче. В SQL тот же вопрос потребовал бы трёх таблиц (person, acted_in, movie) и двух JOIN-ов с условиями по ключам; легко ошибиться в направлении связи или забыть условие. В Cypher вы буквально рисуете «человек снимался в фильме с таким названием» и сразу читаете результат. А «фильмы и их актёры списком» соберём чуть позже через collect.
Агрегаты без GROUP BY
Ещё одна приятная неожиданность для тех, кто из SQL: в Cypher нет отдельного GROUP BY. Группировка происходит автоматически по тем полям в RETURN, которые не являются агрегатами. Например, «сколько актёров в каждом фильме»:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN m.title AS film, count(p) AS actors
ORDER BY actors DESCЗдесь m.title — не агрегат, по нему и идёт неявная группировка; count(p) считает актёров в каждой группе. Функция collect(p.name) вместо count собрала бы имена в список — так получают «фильм и список его актёров одной строкой».
DISTINCT против дублей
Один узел может приходить по нескольким путям, и тогда в результате будут повторы. DISTINCT их убирает:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(co:Person)
RETURN DISTINCT co.name AS colleagueЗдесь мы идём «актёр → фильм → другой актёр того же фильма»; без DISTINCT коллега, снявшийся с человеком в нескольких фильмах, повторился бы.
Как работает под капотом
MATCH порождает поток строк-совпадений: каждая строка — это один найденный экземпляр паттерна с привязанными переменными. RETURN, ORDER BY, LIMIT, DISTINCT — операторы конвейера, которые этот поток преобразуют. Поэтому Cypher читается как конвейер: «найди паттерны → отфильтруй → отсортируй → ограничь → верни». LIMIT, кстати, позволяет планировщику остановить обход раньше, не материализуя все совпадения.
Здесь кроется тонкость, которая объясняет неожиданные числа в результатах. Если паттерн нашёлся тремя разными способами (скажем, человек снялся в трёх фильмах), MATCH выдаст три строки для этого человека — по одной на каждый вариант привязки переменных. Поэтому, когда вы пишете MATCH (p:Person)-[:ACTED_IN]->(m) RETURN p.name, имя плодовитого актёра появится столько раз, во скольких фильмах он снялся. Это не баг — это прямое следствие того, что строка соответствует совпадению паттерна, а не узлу. Понимание этого снимает 90% вопросов «откуда взялись повторы».
Про порядок операторов конвейера. ORDER BY и DISTINCT — операции блокирующие: чтобы отсортировать или выкинуть дубли, движку нужно увидеть весь поток целиком, поэтому он не может отдать первую строку, пока не получил последнюю. А вот LIMIT без сортировки — потоковая операция: набрал нужное число строк и остановился, не трогая остальное. Отсюда практический совет: LIMIT 10 на сыром обходе почти бесплатен, а ORDER BY ... LIMIT 10 сначала отсортирует всё, и только потом возьмёт десятку — это уже полноценная работа над всем потоком.
Частые ошибки
- Забыть DISTINCT при обходах туда-обратно. Пути часто дублируют узлы — результат раздувается.
- Возвращать узел, ожидая таблицу.
RETURN mдаёт граф/объект; для табличного отчёта возвращайте свойстваm.title. - Сортировать по невозвращённому полю в старых версиях. Лучше явно вычислять/возвращать поле, по которому сортируете.
Итоги
MATCHищет паттерн,RETURNвыбирает, что вернуть; вместе — основа чтения.AS,ORDER BY,SKIP,LIMITработают как в SQL.- Обход по связям заменяет JOIN — просто рисуем стрелки.
DISTINCTубирает дубли, неизбежные при обходах по нескольким путям.