Я играл с самым последним выпуском Neo4j 2.0 — 2.0.0-M05 — и одним из первых, что я сделал, был перевод запросов из моего набора данных о футболе, которые были написаны для Neo4j 1.9.
Следующий запрос вычисляет количество голов, забитых игроками в матчах, которые были показаны по телевидению, а не по телевидению и в целом.
START player=node:players('name:*') MATCH player-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT([stats.goals, TYPE(t)]) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) IS NULL)| goals + HEAD(g')) AS nonTvGoals, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) <> NULL)| goals + HEAD(g')) AS tvGoals, REDUCE(goals = 0, g' in games | goals + HEAD(g')) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
Мы используем унаследованный индекс, чтобы найти всех игроков, найти игры, в которых они участвовали, проверить, были ли эти игры по телевизору, а затем сгруппировать по игрокам, чтобы определить, были ли забиты голы в телевизионных играх.
Когда мы оцениваем этот запрос, мы получаем следующий результат:
==> +--------------------------------------------------------+ ==> | player.name | nonTvGoals | tvGoals | totalGoals | ==> +--------------------------------------------------------+ ==> | "Robin Van Persie" | 11 | 15 | 26 | ==> | "Gareth Bale" | 8 | 13 | 21 | ==> | "Luis Suárez" | 12 | 11 | 23 | ==> | "Theo Walcott" | 5 | 9 | 14 | ==> | "Demba Ba" | 7 | 8 | 15 | ==> | "Edin Dzeko" | 7 | 7 | 14 | ==> | "Santi Cazorla" | 5 | 7 | 12 | ==> | "Juan Mata" | 6 | 6 | 12 | ==> | "Steven Gerrard" | 3 | 6 | 9 | ==> | "Carlos Tevez" | 5 | 6 | 11 | ==> +--------------------------------------------------------+ ==> 10 rows
Первым шагом было избавиться от устаревшего индекса и заменить его на метку . Я создал ярлык «Player» для этой цели:
START player = node:players('name:*') SET player :Player RETURN player CREATE INDEX on :Player(name)
Затем я избавился от поиска устаревших индексов и заменил его на метку:
MATCH (player:Player)-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT([stats.goals, TYPE(t)]) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) IS NULL)| goals + HEAD(g')) AS nonTvGoals, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) <> NULL)| goals + HEAD(g')) AS tvGoals, REDUCE(goals = 0, g' in games | goals + HEAD(g')) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
К сожалению, вы не можете использовать дополнительные отношения, как это:
PatternException: Can't use optional patterns without explicit START clause. Optional relationships: `t`
Оптимальный обходной путь, который показал мне Андрес , — сначала сопоставить всех игроков, а затем сделать их доступными с помощью оператора WITH :
MATCH (player:Player) WITH player MATCH player-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT([stats.goals, TYPE(t)]) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) IS NULL)| goals + HEAD(g')) AS nonTvGoals, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE HEAD(TAIL(g)) <> NULL)| goals + HEAD(g')) AS tvGoals, REDUCE(goals = 0, g' in games | goals + HEAD(g')) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
Если мы оценим этот запрос, он вернет нам те же результаты, что и раньше. Однако, учитывая, что теперь у нас улучшена поддержка коллекций, было бы стыдно использовать HEAD и TAIL, поэтому я заменил их следующими:
MATCH (player:Player) WITH player MATCH player-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT([stats.goals, TYPE(t)]) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE g[1] IS NULL)| goals + g'[0]) AS nonTvGoals, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE g[1] <> NULL)| goals + g'[0]) AS tvGoals, REDUCE(goals = 0, g' in games | goals + g'[0]) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
Мне также не понравились эти индексы массивов, и, поскольку теперь мы можем создавать карты, я подумал, что запрос будет более понятным, если мы создадим один в строке 4 запроса, а не массив:
MATCH (player:Player) WITH player MATCH player-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT({goals: stats.goals, ontv: TYPE(t)}) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE g.ontv IS NULL)| goals + g'.goals) AS nonTvGoals, REDUCE(goals = 0, g' IN FILTER(g IN games WHERE g.ontv <> NULL)| goals + g'.goals) AS tvGoals, REDUCE(goals = 0, g' in games | goals + g'.goals) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
The next tweak would be to remove the FILTER and replace that with a list comprehension:
MATCH (player:Player) WITH player MATCH player-[:played|subbed_on]->stats-[:in]->game-[t?:on_tv]->channel WITH COLLECT({goals: stats.goals, ontv: TYPE(t)}) AS games, player RETURN player.name, REDUCE(goals = 0, g' IN [g IN games WHERE g.ontv IS NULL] | goals + g'.goals) AS nonTvGoals, REDUCE(goals = 0, g' IN [g IN games WHERE g.ontv <> NULL] | goals + g'.goals) AS tvGoals, REDUCE(goals = 0, g' in games | goals + g'.goals) AS totalGoals ORDER BY tvGoals DESC LIMIT 10
Although the query ends up being a couple of lines longer than its 1.9 cousin I think there’s less noise and the intent is clearer.