Neo4j: Cypher - Nested Path Comprehensions vs OPTIONAL MATCH
While writing my previous post about Cypher nested path comprehensions, I realised that for this particular problem, the OPTIONAL MATCH clause is a better choice.
To recap, we have the following graph:
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)
We started the post with the following query that returns (club)-[:IN_LEAGUE]→(league)-[:IN_COUNTRY]→(country)
paths:
MATCH (club:Club)
RETURN club.name, [path = (club)-[:IN_LEAGUE]->(league)-[:IN_COUNTRY]->(country) | {path: path}] AS path
club.name | path |
---|---|
Man Utd |
[{path: (:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})}] |
Juventus |
[] |
The equivalent query using the OPTIONAL MATCH
clause looks like this:
MATCH (club:Club)
OPTIONAL MATCH path = (club)-[:IN_LEAGUE]->(league)-[:IN_COUNTRY]->(country)
RETURN club.name, path
If we run that query we get the following result:
club.name | path |
---|---|
Man Utd |
(:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"}) |
Juventus |
NULL |
We concluded the post with the following query that returns as much of the (club)-[:IN_LEAGUE]→(league)-[:IN_COUNTRY]→(country)
as exists:
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])}]
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"})}] |
The equivalent query using the OPTIONAL MATCH
clause looks like this:
MATCH (club:Club)
OPTIONAL MATCH path1 = (club)-[:IN_LEAGUE]->(league)
OPTIONAL MATCH path2 = (league)-[:IN_COUNTRY]->(country)
RETURN club.name, path1, path2
club.name | path1 | path2 |
---|---|---|
Man Utd |
(:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"}) |
(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"}) |
Juventus |
(:Club {name: "Juventus"})-[:IN_LEAGUE]→(:League {name: "Serie A"}) |
NULL |
As in the other post, we can then use APOC's apoc.path.combine
function on these paths:
MATCH (club:Club)
OPTIONAL MATCH path1 = (club)-[:IN_LEAGUE]->(league)
OPTIONAL MATCH path2 = (league)-[:IN_COUNTRY]->(country)
RETURN club.name, apoc.path.combine(path1, path2)
club.name | path |
---|---|
Man Utd |
(:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"}) |
Juventus |
(:Club {name: "Juventus"})-[:IN_LEAGUE]→(:League {name: "Serie A"}) |
That works well when we only have two paths to combine, but what if we have more than that?
Let’s run the following query to create an IN_CONFEDERATION
relationship from England:
MATCH (country:Country {name:"England"})
MERGE (confederation:Confederation {name: "UEFA"})
MERGE (country)-[:IN_CONFEDERATION]->(confederation)
Now we can write the following query to optionally find each of our three relationships, IN_LEAGUE
, IN_COUNTRY
, and IN_CONFEDERATION
:
MATCH (club:Club)
OPTIONAL MATCH path1 = (club)-[:IN_LEAGUE]->(league)
OPTIONAL MATCH path2 = (league)-[:IN_COUNTRY]->(country)
OPTIONAL MATCH path3 = (country)-[:IN_CONFEDERATION]->(confederation)
WITH club, [path1, path2, path3] AS paths
RETURN club.name,
reduce(acc=null, path in paths | apoc.path.combine(acc, path)) AS path
Note that we’re using the reduce
function to iterate over our paths and join them together.
If we run this query we’ll see the following result:
club.name | path |
---|---|
Man Utd |
(:Club {name: "Man Utd"})-[:IN_LEAGUE]→(:League {name: "Premier League"})-[:IN_COUNTRY]→(:Country {name: "England"})-[:IN_CONFEDERATION]→(:Confederation {name: "UEFA"}) |
Juventus |
(:Club {name: "Juventus"})-[:IN_LEAGUE]→(:League {name: "Serie A"}) |
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.