bartgras
bartgras

Reputation: 442

MERGE with 3+ relations coming from single node

I'm looking for a way to create (or if exist, find a matching pattern) for MERGE with 3 and more relationships coming out from single node.

I know how to find/create 2 nodes related to single node, for example:

MERGE (f1:Friend)
MERGE (f2:Friend)
MERGE (w2)<-[:HAS_FRIEND]-(p:Person)-[:HAS_FRIEND]->(w1)
ON CREATE {do something}
ON MATCH SET {do something else}

I understand that you can continue creating/finding more nodes if you add them to right-most or left-most nodes, e.g.

MERGE (friend1)<-[:HAS_FRIEND]-(p:Person)-[:HAS_FRIEND]->(friend2)-[:HAS_PET]->(pet:Pet)-[:HAS_TOY]->(toy:Toy)

But how to MERGE (find if WHOLE pattern exists or create it if not found) something like 3 :Friend nodes coming out of single :Person node

3 nodes related to 1 node

Upvotes: 3

Views: 497

Answers (2)

cybersam
cybersam

Reputation: 66999

You cannot use ON CREATE and ON MATCH in the way you envision, as that would require you to somehow craft a single MERGE clause that could safely and completely handle the rest of your complex use case. That is not possible.

The sample query below shows how to handle your use case (as far as I understand it).

Assumptions:

  • The name of the "me" person is passed in the myName parameter.
  • The list of friend names is passed in the names parameter.
  • All people nodes have the Person label.
  • At most one HAS_FRIEND relationship should exist between a pair of Person nodes. (Notice that the (p)-[:HAS_FRIEND]-(f) pattern does not specify a relationship direction, so it will match either direction.)
  • You are running neo4j 4.x+.

The following query ensures that all the people whose names are in the $names list are connected (in either direction) to the person whose name is given by $myName. It will also conditionally perform the equivalent of "ON CREATE" or "ON MATCH" processing (except on steroids, since you are not limited to SET clauses), depending on whether the query had to create new nodes.

MATCH (me:Person {name: $myName})
WITH me, [(me)-[:HAS_FRIEND]-(f) WHERE f.name IN $names | f] AS foundFriends
CALL {
  WITH me, foundFriends
  UNWIND [x IN $names WHERE NOT x IN [ff IN foundFriends | ff.name] | x] AS neededName
  CREATE (me)-[:HAS_FRIEND]->(newFriend:Person {name: neededName})
  RETURN COLLECT(newFriend) AS newFriends
}
CALL apoc.do.when(
  SIZE(newFriends) > 0,
  'RETURN "CREATED" AS result',
  'RETURN "MATCHED" AS result',
  {me: me, foundFriends: foundFriends, newFriends: newFriends}) YIELD value
RETURN value.result

This query uses the CALL subquery syntax introduced in neo4j 4.0.

And it also uses the APOC procedure apoc.do.when to conditionally process Cypher code (to do something like "ON CREATE" or "ON MATCH" processing). In the sample above, the Cypher code simply returns "CREATED" or "MATCHED". But your actual code could be much more complicated (and also make use of the parameters passed via the {me: me, foundFriends: foundFriends, newFriends: newFriends} argument).

Upvotes: 0

Dave Bennett
Dave Bennett

Reputation: 11216

Not completely sure I follow, but is something like this what you are looking for? Given a list of friends and me merge each Person/Friend mode and then merge the relationship between them as `[:FRIEND].

with ['dave','mary','derek'] as friends
merge (me:Person {name: 'me'})
with me, friends
unwind friends as friend
merge (f:Friend {name: friend})
merge (me)-[:FRIEND]->(f)
return *

If you had a friendship going the other way though and you did not want to create a new relationship in the other direction a second time through you could guard against that with something like this. Say we introduce me's good friend rich into the equation but rich is actually friends in the other direction.

merge (me:Person {name: 'me'})
merge (rich:Friend {name: 'rich'})
merge (me)<-[:FRIEND]-(rich)
return *

Then if we add rich to the list we are trying to merge but add a step after each :Friend is merged to guard against creating one if it already exists in the other direction.

with ['dave','mary','derek','rich'] as friends
merge (me:Person {name: 'me'})
with me, friends
unwind friends as friend
merge (f:Friend {name: friend})
with me, f
match (f)
where not (me)<-[:FRIEND]-(f)
merge (me)-[:FRIEND]->(f)
return *

The match (f) where not (me)<-[:FRIEND]-(f) guards against merging the relationship if it already exists albeit in the other direction.

It prevents this from happening

friends with bi-directional FRIEND relationship

Upvotes: 4

Related Questions