Статьи

Neo4j / Cypher: знакомство с оператором WITH

Несколько недель назад я написал пост, в котором показан пример запроса на шифрование, в котором использовался оператор WITH, но я до сих пор не до конца понимаю, как он работает, поэтому решил написать еще несколько запросов, в которых он используется.

Я хотел выяснить, имеет ли Луис Суарес лучший результат в зависимости от того, в какой день проводится матч.

Мы начнем с поиска всех матчей, в которых он играл и в какие дни были эти матчи:

START player = node:players('name:"Luis Suárez"')
MATCH game-[:in]-stats-[:played]-player, game-[:on_day]-day
RETURN day.name, game.name
+---------------------------------------------------+
| day.name    | game.name                           |
+---------------------------------------------------+
| "Saturday"  | "Liverpool vs Southampton"          |
| "Saturday"  | "Southampton vs Liverpool"          |
| "Saturday"  | "Liverpool vs Reading"              |
| "Saturday"  | "West Bromwich Albion vs Liverpool" |
...
+---------------------------------------------------+
29 rows

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

START player = node:players('name:"Luis Suárez"')
MATCH game-[:in]-stats-[:played]-player, game-[:on_day]-day
RETURN day.name, COUNT(game.name)
+--------------------------------+
| day.name    | COUNT(game.name) |
+--------------------------------+
| "Sunday"    | 13               |
| "Wednesday" | 4                |
| "Monday"    | 1                |
| "Saturday"  | 11               |
+--------------------------------+
4 rows

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

+----------------------------------------------------------------------------------------+
| day.name    | games                                                                    |                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
+----------------------------------------------------------------------------------------+
| "Sunday"    | [["scored_in","Liverpool vs Manchester City"]…]                          |
| "Wednesday" | [[<null>,"Tottenham Hotspur vs Liverpool"],[<null>,"Stoke City vs Liverpool"]...]    |                                                                                                                                                                                                                                                                                                                                                                                          
| "Monday"    | [[<null>,"Liverpool vs West Bromwich Albion"]]                                 |                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
| "Saturday"  | [[<null>,"Liverpool vs Southampton"]…]                                         |                                                         
+----------------------------------------------------------------------------------------+
4 rows

Этот запрос стал немного сложнее, чем наши предыдущие, потому что мы хотели вернуть все дни, когда Суарес сыграл матчи, даже если он не забивал в тот день.

Единственная интересная вещь в первых двух строках состоит в том, что мы необязательно сопоставляем отношение ‘scored_in’, чтобы мы могли справиться с ситуацией, когда Суарес не забил, все еще возвращая строку.

В третьей строке мы возвращаем день, а затем получаем коллекцию кортежей отношения ‘scored_in’ и соответствующей игры.

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

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

START player = node:players('name:"Luis Suárez"')
MATCH game-[:in]-stats-[:played]-player-[r?:scored_in]-game-[:on_day]-day
WITH day, COLLECT(DISTINCT([type(r), game.name])) AS games
RETURN day.name, REDUCE(totalGames = 0, game in FILTER(x in games : head(x) = "scored_in"): totalGames + 1) AS gamesScoredIn
+-----------------------------+
| day.name    | gamesScoredIn |
+-----------------------------+
| "Wednesday" | 2             |
| "Saturday"  | 6             |
| "Sunday"    | 7             |
| "Monday"    | 0             |
+-----------------------------+
4 rows

Мы начинаем с фильтрации игр, чтобы сохранить только те, в которых забил Суарес. Затем мы запускаем REDUCE для полученной коллекции, которая просто добавляет 1 к аккумулятору для каждой записи в коллекции.

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

В итоге мы получаем следующее:

START player = node:players('name:"Luis Suárez"')
MATCH game-[:in]-stats-[:played]-player, game-[:on_day]-day
 
WITH player, day, COUNT(game) AS playedGames
MATCH game-[:in]-stats-[:played]-player-[r?:scored_in]-game-[:on_day]-day
 
WITH day, COLLECT(DISTINCT([type(r), game.name])) AS games, playedGames
WITH day, REDUCE(totalGames = 0, game in FILTER(x in games : head(x) = "scored_in"): totalGames + 1) AS scoredGames, playedGames
RETURN day.name, playedGames, scoredGames, (scoredGames*1.0/playedGames*1.0) * 100 AS percentage
+-------------------------------------------------------------+
| day.name    | playedGames | scoredGames | percentage        |
+-------------------------------------------------------------+
| "Saturday"  | 11          | 6           | 54.54545454545454 |
| "Monday"    | 1           | 0           | 0.0               |
| "Wednesday" | 4           | 2           | 50.0              |
| "Sunday"    | 13          | 7           | 53.84615384615385 |
+-------------------------------------------------------------+
4 rows

Одна вещь, которая меня здесь смущала, это то, что нам нужно передавать все, что мы хотим, в конечном итоге, возвращать в каждом операторе WITH, иначе он не будет нам доступен в конце.

Если мы собираемся сделать несколько операторов MATCH, нам нужно передать начальный узел в предыдущем операторе WITH, что в данном случае означает, что нам нужно передать переменную player.

Кроме этого этот запрос является объединением предыдущих двух, за исключением того, что мы добавили некоторую арифметику в последнюю строку, чтобы вычислить% совпадений, в которых набрал Суарес. Мне пришлось умножить каждое число на 1,0, чтобы форсировать арифметику на основе числа с плавающей запятой, а не целочисленный

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