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

MATCH и RETURN: находим и возвращаем

junior~20 мин

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

Начинается кластер про чтение данных. В уроках про CREATE и MERGE мы строили граф сами, а теперь возвращаемся к готовому графу книжного клуба. Первая пара клауз, без которой не обходится ни один запрос: MATCH находит, RETURN возвращает. Сегодня учимся управлять тем, что именно вернётся: какие колонки, под какими именами, в каком порядке и сколько строк.

Учебный граф

Во всех песочницах кластера лежит один и тот же граф:

  • десять участников с меткой Person, свойства name и born, у Егора год рождения не указан, Яна ни с кем не связана;
  • три города City со свойством name;
  • пять книг Book со свойствами title, author, year;
  • связи KNOWS {since}, LIVES_IN и READ {rating}, у одного отзыва Ильи оценки нет.

Вернуть узлы целиком

Самый простой запрос: найти узлы по метке и вернуть их.

MATCH (c:City)
RETURN c;
+--------------------------+
| c                        |
+--------------------------+
| (:City {name: "Москва"}) |
| (:City {name: "Казань"}) |
| (:City {name: "Пермь"})  |
+--------------------------+

3 rows

MATCH (c:City) находит все узлы с меткой City и называет каждый c. RETURN c отдаёт их целиком, с метками и свойствами. Это удобно, чтобы осмотреться, но в приложении обычно нужны отдельные свойства.

Вернуть свойства

К свойству обращаются через точку: p.name, p.born. Колонка в ответе называется так же, как выражение:

MATCH (p:Person)
RETURN p.name, p.born;
+------------------+
| p.name  | p.born |
+------------------+
| "Аня"   | 1994   |
| "Борис" | 1990   |
| "Вера"  | 1998   |
| "Глеб"  | 1987   |
| "Дина"  | 2001   |
| "Егор"  | NULL   |
| "Жанна" | 1992   |
| "Зоя"   | 1999   |
| "Илья"  | 1985   |
| "Яна"   | 2003   |
+------------------+

10 rows

У Егора года рождения нет, и в его строке NULL. Отсутствующее свойство в Cypher читается как null, ошибки не будет. Про null подробнее в уроке про OPTIONAL MATCH.

Порядок строк здесь совпал с порядком, в котором участники попали в базу. Но это случайность реализации, а не обещание: без ORDER BY база вправе вернуть строки в любом порядке.

Псевдонимы и выражения

AS даёт колонке короткое имя. В RETURN можно писать не только свойства, но и выражения, например возраст книги:

MATCH (b:Book)
RETURN b.title AS title, 2026 - b.year AS age
ORDER BY age DESC;
+------------------------------------------+
| title                              | age |
+------------------------------------------+
| "Обломов"                          | 167 |
| "Двенадцать стульев"               | 98  |
| "Понедельник начинается в субботу" | 61  |
| "Мастер и Маргарита"               | 59  |
| "Пикник на обочине"                | 54  |
+------------------------------------------+

5 rows

После AS имя age можно использовать в ORDER BY. Это псевдоним: имя колонки результата.

ORDER BY, SKIP и LIMIT

ORDER BY сортирует, DESC переворачивает порядок. LIMIT оставляет первые строки, SKIP пропускает строки с начала. Три самых молодых участника:

MATCH (p:Person)
RETURN p.name AS name, p.born AS born
ORDER BY born DESC
LIMIT 3;
+---------------+
| name   | born |
+---------------+
| "Егор" | NULL |
| "Яна"  | 2003 |
| "Дина" | 2001 |
+---------------+

3 rows

Первым оказался Егор, у которого года нет. В Cypher null при сортировке считается больше любого значения, поэтому при DESC он всплывает наверх, а при обычной сортировке уходит в конец. Как отсеять такие строки, покажет урок про WHERE.

SKIP и LIMIT вместе дают страницы. Вторая страница по три имени в алфавитном порядке:

MATCH (p:Person)
RETURN p.name
ORDER BY p.name
SKIP 2 LIMIT 3;
+--------+
| p.name |
+--------+
| "Вера" |
| "Глеб" |
| "Дина" |
+--------+

3 rows

DISTINCT: без повторов

Книг пять, а авторов меньше: у Стругацких две книги. DISTINCT убирает одинаковые строки:

MATCH (b:Book)
RETURN DISTINCT b.author AS author
ORDER BY author;
+-----------------+
| author          |
+-----------------+
| "Булгаков"      |
| "Гончаров"      |
| "Ильф и Петров" |
| "Стругацкие"    |
+-----------------+

4 rows

DISTINCT сравнивает строки целиком. Если вернуть DISTINCT b.author, b.year, строк снова станет пять: пары автор и год у всех книг разные.

Проекция карты

Иногда удобнее вернуть одно значение со всеми нужными полями, например для JSON в приложении. Для этого есть проекция карты:

MATCH (b:Book)
RETURN b {.title, .year} AS card
ORDER BY b.year
LIMIT 2;
+-------------------------------------------+
| card                                      |
+-------------------------------------------+
| {title: "Обломов", year: 1859}            |
| {title: "Двенадцать стульев", year: 1928} |
+-------------------------------------------+

2 rows

Запись b {.title, .year} берёт у узла только два свойства. b {.*} взяла бы все, а b {.title, decade: b.year / 10 * 10} добавила бы своё поле.

Попробуй сам

Запрос возвращает пути от читателей к книгам Стругацких. Картинка покажет, кто какую из двух книг читал.

Перепиши запрос, чтобы он возвращал не пути, а имя читателя и название книги с псевдонимами reader и book, отсортированные по читателю. Потом выведи только первых двух.

Проверь себя

Задание

Дальше

Пока мы возвращали всех подряд или выбирали по точному значению свойства. В следующем уроке добавим WHERE: сравнения, диапазоны, поиск по части строки и условия на связи.

домашка

Домашка