2
votes

Building on this similar question, I want the most performant way to handle this scenario.

MERGE (n1{id:<uuid>})
SET n1.topicID = <unique_name1>
IF (EXISTS((a:Topic{id:<unique_name1>})) |  CREATE UNIQUE (n1)-[:HAS]->(a))

MERGE (n2{id:<uuid>})
SET n2.topicID = <unique_name2>
IF (EXISTS((a:Topic{id:<unique_name2>})) |  CREATE UNIQUE (n2)-[:HAS]->(a))

Unfortunately, IF doesn't exist, and EXISTS can't be used to match or find a unique node.

  • I can't use OPTIONAL MATCH, because then CREATE UNIQUE will throw a null exception (as much as I wish it would ignore null parameters)
  • I can't use MATCH, because if the topic doesn't exist, I will will loose all my rows.
  • I can't use MERGE, because I don't want to create the node if it doesn't exist yet.
  • I can't use APOC, because I have no guarantee that it will be available for use on our Neo4j server.

The best solution I have right now is

MERGE (a:TEST{id:1})
WITH a
OPTIONAL MATCH (b:TEST{id:2})
// collect b so that there are no nulls, and rows aren't lost when no match
WITH a, collect(b) AS c
FOREACH(n IN c | CREATE UNIQUE (a)-[:HAS]->(n))
RETURN a

However, this seems kinda complicated and needs 2 WITHs for what is essentially CREATE UNIQUE RELATION if start and end node exist (and in the plan there is an eager). Is it possible to do any better? (Using Cypher 3.1)

1

1 Answers

6
votes

You can simplify a quite a bit:

MERGE (a:TEST{id:1})
WITH a
MATCH (b:TEST{id:2})
CREATE UNIQUE (a)-[:HAS]->(b)
RETURN a;

The (single) WITH clause serves to split the query into 2 "sub-queries".

So, if the MATCH sub-query fails, it only aborts its own sub-query (and any subsequent ones) but does not roll back the previous successful MERGE sub-query.

Note, however, that whenever a final sub-query fails, the RETURN clause would return nothing. You will have to determine if this is acceptable.

Because the above RETURN clause would only return something if b exists, it might make more sense for it to return b, or the path. Here is an example of the latter (p will be assigned a value even if the path already existed):

MERGE (a:TEST{id:1})
WITH a
MATCH (b:TEST{id:2})
CREATE UNIQUE p=(a)-[:HAS]->(b)
RETURN p;

[UPDATE]

In neo4j 4.0+, CREATE UNIQUE is no longer supported, so MERGE needs to be used instead.

Also, if you want to return a even if b does not exist, you can use the APOC function apoc.do.when:

MERGE (a:TEST{id:1})
WITH a
OPTIONAL MATCH (b:TEST{id:2})
CALL apoc.do.when(
  b IS NOT NULL,
  'MERGE (a)-[:HAS]->(b)',
  '',
  {a: a, b: b}) YIELD value
RETURN a;