How to chain UNWIND clauses

I am attempting to use two separate lists to create nodes/relationships in cypher.

My current solution is:

MERGE (t:Test)
UNWIND ["a","b"] AS name                      // first list
MERGE (t)-[:FOO_REL]->(a:Foo { name: name })
UNWIND [100,200] AS id                        // second list
MERGE (t)-[:BAR_REL]->(b:Bar { id: id })

This works as long as both lists have entries in them. However, if the first list is empty then the second list is never unwound (and no :Bar nodes are created).

How would I go about chaining UNWIND s such that I can create nodes/relationships from two separate lists in a single query?

I ended up using FOREACH instead of UNWIND . Since this uses parentheses around the inner query, they are clearly delimited from eachother:

MERGE (t:Test)
FOREACH (name in ["a","b"] | 
  MERGE (t)-[:FOO_REL]->(a:Foo { name: name })
FOREACH (id in [100,200] |
  MERGE (t)-[:BAR_REL]->(b:Bar { id: id })
1 Like

Using FOREACH is the quicker solution for this.

If you needed to use UNWIND, we do have some documentation on this effect (as this is a cartesian product of each row with each element of the list on the row, an empty list means the row is wiped out, since it's a x 0), as well as a workaround (using CASE to unwind [null] instead, the single null element will ensure the row stays).

This helped me for the current scenario as i did not had any kind of match statement, but if i have a match statement, i think i need to use unwind... in that case how to chain two or more unwinds together... i tried for a create case where my last unwind always repeated to the array length of the first unwind

So for the following query

with dt 
                     unwind [{ name1:"value", name2: "value" }, { name1:"value", name2: "value" }] as propertyObj
                     create (dt)-[:HAS_DTP]->(p:DTP { 
                         name1: propertyObj.name1, 
                         name2: propertyObj.name2
                     with dt 
                 unwind [{ name1:"value", name2: "value" }, { name1:"value", name2: "value" }] as variableObj
                 create (dt)-[:HAS_DTV]->(tv:DTV:RDTV { 
                      name1: variableObj.name1, 
                      name2: variableObj.name2,  
                 with dt 
                  return dt.uuid as id

the last is running 4 times but it should run 2 times as you see.

I honestly ain't sure what you're asking, but you certainly don't need to use UNWIND just because you're using MATCH. Here's an example for the movie recommendation dataset that comes with Neo4J:

MATCH (m:Movie {title:"Toy Story"})
FOREACH (usr in [{ name: "birjolaxew" }, { name: "mithun.das"}] |
  CREATE (m)-[:TEST_REL]->(u:User {name:})
FOREACH (dir in [{ name: "foo" }, { name: "baz" }] |
  CREATE (m)-[:TEST_REL_2]->(u:Director {name: dir.value1 })

First of all thank you for taking the time to answer my question.
Secondly I am sorry if I could not represent my question properly...
My question was bascially how to run two unwind queries simultaneously.
In my sample query... the last unwind should create 2 DTV instead it was creating 4.
I solved it using Foreach.
However... i was thinking of case when say i have to match, delete and create is that also possible via Foreach i will defintely give a try. Also somehow if i have to use uniwnd in any case, how to use it together or you advise to never do it?

Think of unwind like FOR loop in any language with all the code below unwind is in the scope.

When you add another unwind below an unwind it's like you are doing a nested FOR loop.

More unwinds you add more nested loops you are adding. So, the total number of iterations the lowest unwind segment is multiple of all unwind data.

FOREACH limits the scope, so it does not repeat the logic.

Hope this clears your confusion.

1 Like

You just need to understand what it's doing, and whether that's the right thing for your query. You need to think about the cardinality, the number of rows being processed, as this is what's causing the duplications.

In your example we can clearly see the number of entries in each list, so that helps illustrate what's going on. Remember that Cypher operations execute per row (and this is what allows UNWIND to have an iterative effect even though it's not really an iterating structure like FOREACH):

Assuming at the staring WITH that we have 1 row.

At the first UNWIND we now have entry rows x the number of list elements, so 1 x 2 = 2 rows.

The CREATE occurs, creating the pattern once for each row. 2 :DTP nodes are created, with a relationship created to each.

The with dt does NOT alter the cardinality, you still have 2 rows, with the same dt node for each.

The next UNWIND executes: for each of the 2 input rows, they are multiplied by the number of list elements, so 2 x 2 = 4 rows.

The CREATE executes for each row, 4 new :DTV:RDTV nodes are created.

The with dt again does not change the cardinality, you have 4 rows with the same dt for each.

4 rows are returned with the same dt node's uuid property as id.

If you wanted to reset the cardinality at each WITH clause, use WITH DISTINCT dt or WITH dt, count(dt) or a similar aggregation to ensure dt is distinct.

1 Like

Yes it does.... thank you taking time to explain... my doubts are really clear on this :slight_smile:

And this is the answer I was exactly looking for ..... it is clear to me how to use UNWIND now... thank you @andrew.bowman

How would you go about this with a FOREACH if you wanted to map relationships from a to 100, and b to 200 in the same query?

I think you'd need to add to your description, I'm not quite following your use case.

Though keep in mind that you cannot MATCH or otherwise introduce variables within a foreach and pass them back out, so if you're looking for a means to generate something from a FOREACH, and work with them outside of the FOREACH in the query (not just writing them to the graph), then FOREACH is not the right approach. You'd likely need to turn to UNWIND unless there's an APOC proc or function that could help.

Hi @birjolaxew, you can use a check up. If first list is empty your query will continue.

MERGE (t:Test)
WITH t, ['A', 'B'] as list
'UNWIND list AS name  MERGE (t)-[:FOO_REL]->(a:Foo { name: name }) RETURN name', 
YIELD value                      // first list
UNWIND [100,200] AS id                        // second list
MERGE (t)-[:BAR_REL]->(b:Bar { id: id })