Neo4j: Cypher - UNWIND vs FOREACH
I’ve written a couple of posts about the new UNWIND clause in Neo4j’s cypher query language but I forgot about my favourite use of UNWIND, which is to get rid of some uses of FOREACH from our queries.
Let’s say we’ve created a timetree up front and now have a series of events coming in that we want to create in the database and attach to the appropriate part of the timetree.
Before UNWIND existed we might try to write the following query using FOREACH:
WITH [{name: "Event 1", timetree: {day: 1, month: 1, year: 2014}},
{name: "Event 2", timetree: {day: 2, month: 1, year: 2014}}] AS events
FOREACH (event IN events |
CREATE (e:Event {name: event.name})
MATCH (year:Year {year: event.timetree.year }),
(year)-[:HAS_MONTH]->(month {month: event.timetree.month }),
(month)-[:HAS_DAY]->(day {day: event.timetree.day })
CREATE (e)-[:HAPPENED_ON]->(day))
Unfortunately we can’t use MATCH inside a FOREACH statement so we’ll get the following error:
Invalid use of MATCH inside FOREACH (line 5, column 3)
" MATCH (year:Year {year: event.timetree.year }), "
^
Neo.ClientError.Statement.InvalidSyntax
We can work around this by using MERGE instead in the knowledge that it’s never going to create anything because the timetree already exists:
WITH [{name: "Event 1", timetree: {day: 1, month: 1, year: 2014}},
{name: "Event 2", timetree: {day: 2, month: 1, year: 2014}}] AS events
FOREACH (event IN events |
CREATE (e:Event {name: event.name})
MERGE (year:Year {year: event.timetree.year })
MERGE (year)-[:HAS_MONTH]->(month {month: event.timetree.month })
MERGE (month)-[:HAS_DAY]->(day {day: event.timetree.day })
CREATE (e)-[:HAPPENED_ON]->(day))
If we replace the FOREACH with UNWIND we’d get the following:
WITH [{name: "Event 1", timetree: {day: 1, month: 1, year: 2014}},
{name: "Event 2", timetree: {day: 2, month: 1, year: 2014}}] AS events
UNWIND events AS event
CREATE (e:Event {name: event.name})
WITH e, event.timetree AS timetree
MATCH (year:Year {year: timetree.year }),
(year)-[:HAS_MONTH]->(month {month: timetree.month }),
(month)-[:HAS_DAY]->(day {day: timetree.day })
CREATE (e)-[:HAPPENED_ON]->(day)
Although the lines of code has slightly increased the query is now correct and we won’t accidentally correct new parts of our time tree.
We could also pass on the event that we created to the next part of the query which wouldn’t be the case when using FOREACH.
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.