cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Can UNWIND iterate over specific portion of the query only

Hi all,

I want to know if it is possible when using UNWIND to only iterate a certain part of the query instead of iterating the whole thing.
This is a simplified example but gets my point across

UNWIND $user AS user
CREATE (u:User)
SET u += user
WITH u

// Attaching house to user
UNWIND $houses AS house
MATCH (h:House)
WHERE h.address = house.address
CREATE (u)-[:OWNS]->(h)
WITH u

// Attaching car to user
UNWIND $cars AS car
MATCH (c:Car)
WHERE c.brand = car.brand
CREATE (u)-[:OWNS]->(c)
RETURN DISTINCT c

In this example, I am creating a new user node and attaching the houses and cars the user owns. The parameter $user is only single but for parameter $houses and $cars can have multiple. Thus if I use the above query I will create duplicate relationship between user and car. I know this can be solved by using MERGE instead but MERGE is more costly than CREATE. Back to my original statement;
I want to know if it is possible when using UNWIND to only iterate a certain part of the query instead of iterating the whole thing? (i.e. when using UNWIND $houses just iterate the attaching house part of the query)

Thanks in advance

1 ACCEPTED SOLUTION

The short answer is that you can use MERGE to find or create the relationship - this will give you one relationship rather than many.

The more important point though is that you should be careful about cardinality here. Say if you have one user, 2 cars and 2 houses, using UNWIND will leave you with four rows, meaning that the (u)-[:OWNS]->(c) operation will be run four times and you'll end up with four relationships. For each house, you will get two cars.

To illustrate the point, you can run this query:

with 1 as user
unwind range(1, 2) as house
unwind range(1, 2) as car
RETURN *

The user is present on every row, then for each house there are two rows which represent the individual cars.

So you have two options:

  1. Add a distinct OR collect step after each create to return the cardinality back down to 1
// Distinct
UNWIND $houses AS house
MATCH (h:House)
WHERE h.address = house.address
CREATE (u)-[:OWNS]->(h)
WITH DISTINCT u // <-- back to 1 row per user

or

// Aggregate
UNWIND $houses AS house
MATCH (h:House)
WHERE h.address = house.address
CREATE (u)-[:OWNS]->(h)
WITH u, collect(h) AS houses // <-- user: 1, houses: [1,2]
  1. If you are running a simple write operation you can use FOREACH - this is similar to UNWIND except it only happens after the pipe (|). Note, you'll have to use MERGE rather than MATCH inside the clause.
UNWIND $user AS user
CREATE (u:User)
SET u += user

// Attaching house to user
FOREACH (house IN $houses | 
  MERGE (h:House {address: house.address})
  MERGE (u)-[:OWNS]->(h)
)

// Attaching car to user
FOREACH (car in $cars |
  MERGE (c:Car {brand: car.brand})
  MERGE (u)-[:OWNS]->(c)
)

Which one you use depends on whether you need to return the cars/houses or not.

Hope that helps!

View solution in original post

4 REPLIES 4

ameyasoft
Graph Maven

Post your data model as the answer depends what you have in your data model.

@ameyasoft
Data model:
A user can own multiple cars or multiple houses

The short answer is that you can use MERGE to find or create the relationship - this will give you one relationship rather than many.

The more important point though is that you should be careful about cardinality here. Say if you have one user, 2 cars and 2 houses, using UNWIND will leave you with four rows, meaning that the (u)-[:OWNS]->(c) operation will be run four times and you'll end up with four relationships. For each house, you will get two cars.

To illustrate the point, you can run this query:

with 1 as user
unwind range(1, 2) as house
unwind range(1, 2) as car
RETURN *

The user is present on every row, then for each house there are two rows which represent the individual cars.

So you have two options:

  1. Add a distinct OR collect step after each create to return the cardinality back down to 1
// Distinct
UNWIND $houses AS house
MATCH (h:House)
WHERE h.address = house.address
CREATE (u)-[:OWNS]->(h)
WITH DISTINCT u // <-- back to 1 row per user

or

// Aggregate
UNWIND $houses AS house
MATCH (h:House)
WHERE h.address = house.address
CREATE (u)-[:OWNS]->(h)
WITH u, collect(h) AS houses // <-- user: 1, houses: [1,2]
  1. If you are running a simple write operation you can use FOREACH - this is similar to UNWIND except it only happens after the pipe (|). Note, you'll have to use MERGE rather than MATCH inside the clause.
UNWIND $user AS user
CREATE (u:User)
SET u += user

// Attaching house to user
FOREACH (house IN $houses | 
  MERGE (h:House {address: house.address})
  MERGE (u)-[:OWNS]->(h)
)

// Attaching car to user
FOREACH (car in $cars |
  MERGE (c:Car {brand: car.brand})
  MERGE (u)-[:OWNS]->(c)
)

Which one you use depends on whether you need to return the cars/houses or not.

Hope that helps!

Hello @adam.cowley
Thank you. This helped a lot

Nodes 2022
Nodes
NODES 2022, Neo4j Online Education Summit

All the sessions of the conference are now available online