Статьи

Neo4j / Cypher: функциональность SQL в стиле GROUP BY

Как я упоминал в предыдущем посте, я играл с некоторыми футбольными данными за последние несколько дней, и один из моих запросов (с использованием шифра ) состоял в том, чтобы найти всех игроков, которых отправили в этом сезоне в Премьер-лигу.

Модель на графике отсылок выглядит следующим образом:

Отправка офф

Мой начальный запрос выглядел так:

START player = node:players('name:*')
MATCH player-[:sent_off_in]-game-[:in_month]-month
RETURN player.name, month.name

Сначала мы получаем имена всех игроков, которые хранятся в индексе, а затем следим за отношениями с играми, в которые они были отправлены, а затем выясняем, в какие месяцы были сыграны эти игры.

Этот запрос возвращает:

+----------------------------+
| player.name  | month.name  |
+----------------------------+
| "Jenkinson"  | "February"  |
| "Chico"      | "September" |
| "Odemwingie" | "September" |
| "Agger"      | "August"    |
| "Cole"       | "December"  |
| "Whitehead"  | "August"    |
...
+----------------------------+

Я подумал, что было бы интересно посмотреть, сколько отправок было в каждом месяце, чего мы добились в SQL, используя GROUP BY.

Cypher имеет множество функций агрегации, которые позволяют нам достичь того же результата.

В нашем случае мы хотим использовать функцию COUNT и хотим, чтобы нашим ключом группировки был месяц года, поэтому мы должны включить его и в нашу инструкцию RETURN:

START player = node:players('name:*')
MATCH player-[:sent_off_in]-game-[:in_month]-month
RETURN COUNT(player.name) AS numberOfReds, month.name
ORDER BY numberOfReds DESC

который возвращает:

+----------------------------+
| numberOfReds | month.name  |
+----------------------------+
| 7            | "October"   |
| 6            | "December"  |
| 4            | "September" |
| 4            | "November"  |
| 3            | "August"    |
| 2            | "January"   |
| 2            | "February"  |
+----------------------------+

Насколько я могу судить, все, что не является агрегатной функцией, используется как часть ключа группировки, что означает, что мы можем включить более одного поля в наш ключ группировки.

Это не особенно актуально для нас для этого конкретного запроса, но было бы полезно, если бы мы добавили команды, за которые играют игроки.

Я расширил график, включив в него статистику игрока для каждой игры, которая также включает отношения, указывающие, за какую команду они играли в конкретной игре.

Модель теперь выглядит так:

Статистика

Теперь это выглядит немного сложнее, но это был лучший способ, который я мог придумать для моделирования конкретных деталей игрока для матча.

Я не видел другого способа моделирования того факта, что игрок играл за определенную команду в матче, который я хочу использовать для некоторых других запросов, но если вы можете увидеть более простой способ, пожалуйста, дайте мне знать.

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

START player = node:players('name:*')
MATCH player-[:sent_off_in]-game-[:in_month]-month, 
      game-[:in_match]-stats-[:stats]-player, 
      stats-[:played_for]-team
RETURN player.name, month.name, team.name
ORDER BY month.name

Исходный запрос проходил путь от игрока к играм, в которые они были отправлены, а затем от игр к месяцу, в который игра проводилась.

Теперь мы добавили обход из игры в игровую статистику для этого игрока, а также перешли от игровой статистики к командному узлу, за который игрок играл в этой игре.

Когда мы запускаем это, мы получаем следующие результаты:

+--------------------------------------------+
| player.name  | month.name  | team.name     |
+--------------------------------------------+
| "Agger"      | "August"    | "Liverpool"   |
| "Whitehead"  | "August"    | "Stoke"       |
...
| "Shotton"    | "December"  | "Stoke"       |
| "Nzonzi"     | "December"  | "Stoke"       |
| "Jenkinson"  | "February"  | "Arsenal"     |
...
| "Ivanovic"   | "October"   | "Chelsea"     |
| "Torres"     | "October"   | "Chelsea"     |
+--------------------------------------------+

Итак, мы видим, что в декабре «Сток» отправил 2 игроков, а в октябре — «Челси».

Мы можем написать следующий запрос, чтобы вернуть набор результатов, который использует команду и месяц в качестве ключа группировки, то есть мы подсчитываем, сколько существует путей с одинаковой командой и месяцем:

START player = node:players('name:*')
MATCH player-[:sent_off_in]-game-[:in_month]-month, 
      game-[:in_match]-stats-[:stats]-player, 
      stats-[:played_for]-team
RETURN month.name, team.name, COUNT(player.name) AS numberOfReds
ORDER BY numberOfReds DESC

Когда мы запускаем этот запрос, мы видим следующие результаты:

+--------------------------------------------+
| month.name  | team.name     | numberOfReds |
+--------------------------------------------+
| "December"  | "Stoke"       | 2            |
| "October"   | "Chelsea"     | 2            |
...
| "August"    | "Stoke"       | 1            |
| "November"  | "Tottenham"   | 1            |
| "December"  | "Everton"     | 1            |
+--------------------------------------------+

Все это более подробно объясняется в документации, но я подумал, что было бы интересно написать об этом с точки зрения того, кто более привык писать SQL и пытаться понять, как добиться того же в шифре.