Neo4j: Cypher - Nested Path Comprehensions
I’ve recently been building an application using the GRANDstack, which uses nested Cypher path comprehensions to translate GraphQL queries to Cypher ones. I’d not done this before, so I was quite curious how this feature worked. We’ll explore it using the following dataset:
MERGE (club:Club {name: "Man Utd"})
MERGE (league:League {name: "Premier League"})
MERGE (country:Country {name: "England"})
MERGE (club)-[:IN_LEAGUE]->(league)
MERGE (league)-[:IN_COUNTRY]->(country)
MERGE (club2:Club {name: "Juventus"})
MERGE (league2:League {name: "Serie A"})
MERGE (club2)-[:IN_LEAGUE]->(league2)
If we want to return a path containing a club, the league they play in, and the country that the league belongs to, we could write the following query:
MATCH (club:Club)
RETURN club.name, [path = (club)-[:IN_LEAGUE]->(league)-[:IN_COUNTRY]->(country) | {path: path}] AS path
If we execute that query, we’ll get the following result:
club.name | path |
---|---|
Man Utd |
[{path: (:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})}] |
Juventus |
[] |
We only get a path back for Man Utd because there isn’t a path from Juventus that has both the IN_LEAGUE
and IN_COUNTRY
relationships.
What about if we do one path comprehension to get the IN_LEAGUE
part of the path, and a second one to get the IN_COUNTRY
part of the path?
MATCH (club:Club)
RETURN club.name,
[path1 = (club)-[:IN_LEAGUE]->(league) |
[path2 = (league)-[:IN_COUNTRY]->(country) |
{path1: path1, path2: path2}]] AS path
If we execute that query, we’ll get the following result:
club.name | path |
---|---|
Man Utd |
[[{path1: (:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"}), path2: (:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})}]] |
Juventus |
[[]] |
Hmmm, we expected not to get a value for path2
for Juventus, but we should have got a value for path1
.
How do we do that?
We need to pull the returning the map earlier in the path comprehension so that we return path1
regardless of whether path2
exists.
The following query does this:
MATCH (club:Club)
RETURN club.name,
[path1 = (club)-[:IN_LEAGUE]->(league) |
{path1: path1,
path2: [path2 = (league)-[:IN_COUNTRY]->(country) | path2]}]
If we execute that query, we’ll get the following result:
club.name | path |
---|---|
Man Utd |
[{path1: (:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"}), path2: [(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})]}] |
Juventus |
[{path1: (:Club {name: "Juventus"})-[:IN_LEAGUE]→(:League {name: "Serie A"}), path2: []}] |
That’s more like it!
Man Utd have paths for path1
and path2
, whereas Juventus only have a path for path2
.
Now, we’d like to join those paths together, which we can do using the apoc.path.combine
function from the APOC library:
MATCH (club:Club)
WITH club,
[path1 = (club)-[:IN_LEAGUE]->(league) |
{path1: path1,
path2: [path2 = (league)-[:IN_COUNTRY]->(country) | path2]}] AS path
RETURN club.name,
[item in path | {path: apoc.path.combine(item.path1, item.path2[0])}]
Since we know there can only be one IN_COUNTRY
relationship between league
and country
we select the first value in the path2
array on the last line of the query.
If we execute that query, we’ll get the following result:
club.name | path |
---|---|
Man Utd |
[{path: (:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})}] |
Juventus |
[{path: (:Club {name: "Juventus"})-[:IN_LEAGUE]→(:League {name: "Serie A"})}] |
This example shows how to write a nested path comprehension at just one level of nesting. GraphQL queries can have an indefinite number of levels, so if you want to see extreme usage of this feature of the Cypher query language you’ll need to create a GRANDstack app and then inspect the Cypher queries that get generated.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.