Using MERGE based on multiple "matches"

Hello,
I have been using neo4j for about 7 month now, and I came across a problem.
I wanted to create a node based on multiple conditions,
I have a collection of nodes, and I want to create a node that is connected to all of them.
The problem is that those nodes may already have a node that is connected to all of them.
So, I want to MERGE the node, not CREATE it.
Is it possible to do? do merge base on multiple relations?

thank you very much :slight_smile:

Hello Michael,

Yes MERGE is the way to do this. This will ensure that there is at most one relationship of type :X between the two nodes.

You might want to consider looking at some of the APOC procedures that can be used to make the creation of multiple relationships, especially with collections of nodes, easier.

Elaine

This is actually a tricky case, as currently MERGE can only consider a single pattern, and not multiple comma-separated patterns. As a result, you could only consider at most two relationship to the candidate node, one each from two existing nodes: MERGE (a)-[:REL]->(c:Between_Node)<-[:REL]-(b)

We do have an improvement request in to allow for a multi-pattern MERGE like so:

MERGE (a)-[:REL]->(d:Between_Node), (b)-[:REL]->(d), (c)-[:REL]->(d)

In the meantime, to avoid race conditions, you may want to first do an OPTIONAL MATCH using multiple patterns, and if such a node doesn't exist, lock on all the other nodes involved (APOC's locking procedures would be the easiest way...otherwise you may want to add and then immediately delete a label to engage write locks on each of those nodes), and then OPTIONAL MATCH for that node again (as it may have been created between the time you first checked, and when you took your locks), and if it doesn't exist MERGE the pattern piecemeal to each node in turn.

I understand your solution, what will you do if the nodes (a, b, c) are in a collection, and there can be random number of nodes to check and connect?
Is there an option without explicitly writing the OPTIONAL MATCH for each node?

thank you :slight_smile:

For a collection of nodes, you could use an all() list predicate to check if all of the nodes have the required connection to some other node (let's say a node d).

// assume myList is already in scope with the nodes we want to connect via a central node
CALL apoc.lock.nodes(myList) // let's lock ahead this time
WITH head(myList) as first, myList
OPTIONAL MATCH (first)-[:REL]->(d:SomeLabel)
WHERE all(node in tail(myList) WHERE (node)-[:REL]->(d))
WITH first, myList
WHERE d IS NULL
MERGE (first)-[:REL]->(d:SomeLabel)
FOREACH (node in tail(myList) | MERGE (node)-[:REL]->(d))

If you need to continue to work with that central node in the query after this, then you may need to move the WHERE d IS NULL check and creation into an apoc.do.when() conditional proc that will yield back the node after it's created.

3 Likes

I'm still personally struggling to implement what I want in neo4j v4.2.0

I've copied your solution above but i can't make it run. It always throws a key not found: VariableSlotKey(d) error

// using three extant nodes, merge a child node
// THe child should only be created if the whole pattern doesnt exist

MERGE (parent1:Parent1 {parent1id: 0})
MERGE (parent2:Parent2 {parent2id: 0})
MERGE (parent3:Parent3 {parent3id: 0})
WITH *, [parent1, parent2, parent3] as parents

CALL apoc.lock.nodes(parents) // let's lock ahead this time
WITH head(parents) as first, parents
OPTIONAL MATCH (first)-[:REL]->(d: Child {childi: 0})
WHERE all(node in tail(parents) WHERE (node)-[:REL]->(d))
WITH first, parents, d
WHERE d IS NULL
MERGE (first)-[:REL]->(newd: Child {childi: 0})
FOREACH (node in tail(parents) | MERGE (node)-[:REL]->(newd))

RETURN *

I've managed to make it run. But it will not work if you use comprehensions... So its a bit limited:

// using three extant nodes, merge a child node
// THe child should only be created if the whole pattern doesnt exist

MERGE (parent1:Parent1 {parent1id: 0})
MERGE (parent2:Parent2 {parent2id: 0})
MERGE (parent3:Parent3 {parent3id: 0})
WITH *, [parent1, parent2, parent3] as parents

CALL apoc.lock.nodes(parents) // let's lock ahead this time
OPTIONAL MATCH (parent1)-[:REL]->(d: Child {childi: 0})
WHERE (parent2)-[:REL]->(d) AND (parent3)-[:REL]->(d)
WITH parent1, parent2, parent3, d
WHERE d IS NULL
MERGE (parent1)-[:REL]->(newd:Child)
MERGE (parent2)-[:REL]->(newd)
MERGE (parent3)-[:REL]->(newd)

RETURN *