Neo4j & Cypher: UNWIND vs FOREACH
Join the DZone community and get the full member experience.
Join For FreeI’ve written a couple of posts about the new UNWIND clause in Neo4j’s cypher query language, but I forgot about my favorite 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.
Published at DZone with permission of Mark Needham, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments