IF...ELSE with Cypher Neo4J

12,643

Solution 1

APOC Procedures just updated with support for conditional cypher execution. You'll need version 3.1.3.7 or greater (if using Neo4j 3.1.x), or version 3.2.0.3 or greater (if using Neo4j 3.2.x).

Here's an example of some of the cases you mentioned, using the new procedures:

CALL apoc.when($refs.client IS NOT NULL, 
 "MATCH (cl:client {uuid: refs.client}) RETURN cl", '', {refs:$refs}) YIELD value
WITH value.cl as cl  // which might be null...
...

...
CALL apoc.do.when(cl IS NOT NULL, 
 "DELETE tcr 
  MERGE (t)-[:references]->(cl)", '', {tcr:tcr, t:t, cl:cl}) YIELD value
...

...
RETURN {
  client: cl {.uuid}, ...
}

In your return, map projection is enough to meet your needs, you'll get an object with the uuid if cl exists, or a null for client if not.

Solution 2

Older question, but there are some new tricks to achieve conditional Cypher execution, especially with subqueries introduced in Neo4j 4.1.x.

This knowledge base article covers the options available, and we'll keep the article updated as additional approaches become available.

https://neo4j.com/developer/kb/conditional-cypher-execution/

Note that the APOC approach in my older answer is still valid, and included in the article.

Share:
12,643
Senica Gonzalez
Author by

Senica Gonzalez

Passionate about solving problems. Whether that is wood working or programming, finding a creative solution to a difficult problem is satisfying. I grew up in rural North Carolina where programmers were scarce. Programming for me was an escape...a way to get in touch with technology and the outside world, a way to dream of doing something meaningful and impacting others.

Updated on June 09, 2022

Comments

  • Senica Gonzalez
    Senica Gonzalez almost 2 years

    Has there been any update to the syntax of an IF/ELSE statement in Cypher?

    I know about CASE and the FOREACH "hacks" but they are so unsightly to read :)

    I was wanting to do something with optional parameters such as:

    CASE WHEN exists($refs.client) THEN MATCH (cl:client {uuid: $refs.client}) END
    ...
    
    // and later use it like
    CASE WHEN exists(cl) THEN DELETE tcr MERGE (t)-[:references]->(cl) END
    
    // and again in my return
    RETURN {
      client: CASE WHEN exists(cl) THEN {uuid: cl.uuid} ELSE NULL END,
    }
    

    I know that doesn't make a lot of sense given the context, but I'm basically passing in a refs object which may or may not contain parameters (or the parameters exist and are NULL)

    Somewhere I read there might be an update to how an "if/else" may be handled in neo4j so I really just wanted to check in and see if anyone was aware of a "nicer" way to handle cases like this.

    Currently, I just handle all my queries in code and run a bunch of smaller queries, but it requires duplicate lookups for creating and deleting references. I'd like to move it all into one larger query so I can use variable references.

    Again, I know I could use FOREACH...CASE, but when there is a lot of smaller cases like this, it gets hairy.

    Currently the error is

    { Error: Invalid input 'S': expected 'l/L' (line 7, column 9 (offset: 246))
    "      CASE true WHEN exists($refs.client) THEN MATCH (cl:client {uuid: $refs.client}) END"
             ^
    

    I also know that I can use WITH...CASE if I'm passing back a known value, but cannot do a MATCH inside it.

    One of the reasons for wanting to do MATCH inside the CASE at the top of the query, is because I want the query to fail if the property on refs exists but the MATCH does not succeed. Using OPTIONAL MATCH does not accomplish this.

    EDIT Oh, also... I'm reviewing using MATCH (cl:client {uuid: $refs.client}) WHERE exists($refs.client) but I recall that not working correctly.

    EDIT I can do MATCH...WHERE exists() but later it's futile if I can't do MERGE WHERE exists()

    EDIT For reference to show why I'm asking about an IF/ELSE, here is the query I'm looking at. I've modified it from the above example so it doesn't error out.

    MATCH (u:user {uuid: $uid})-[:allowed_to {read: true}]->(c:company {uuid: $cid})
    MATCH (t:timesheet {uuid: $tid})<-[:owns]-(:timesheets)<-[:owns]-(u)
    
    // Make sure the incoming references are valid or fail query
    // Here, I'd like only do a match IF $refs.client exists and IS NOT NULL. If it is null or does not exist, I don't want the query to fail. OPTIONAL MATCH will not fail if the value is passed in is invalid but will simply return NULL. Which is why IF/ELSE (or CASE) would be helpful here.
    MATCH (cl:client {uuid: $refs.client})
    MATCH (ca:case {uuid: $refs.case})
    MATCH (s:step {uuid: $refs.step})
    MATCH (n:note {uuid: $refs.note})
    
    // clone timesheet entry to a revision
    CREATE (t)-[:assembled_with]->(r:revision)
    SET r = t, r.created_at = $data.updated_at
    
    WITH *
    
    // Get the old references
    MATCH (t)-[tcr:references]->(rc:client)
    MATCH (t)-[tcar:references]->(rca:case)
    MATCH (t)-[tsr:references]->(rs:step)
    MATCH (t)-[tnr:references]->(rn:note)
    
    // Copy old references to revision (won't create new relationships with NULL)
    MERGE (r)-[:references]->(rc)
    MERGE (r)-[:references]->(rca)
    MERGE (r)-[:references]->(rs)
    MERGE (r)-[:references]->(rn)
    
    // Update the current timesheet with new data
    SET t += $data
    
    // If new references are incoming, delete the old ones and update for new ones
    DELETE tcr
    DELETE tcar
    DELETE tsr
    DELETE tnr
    MERGE (t)-[:references]->(cl)
    MERGE (t)-[:references]->(ca)
    MERGE (t)-[:references]->(s)
    MERGE (t)-[:references]->(n)
    
    WITH *
    
    // Get the new count of revisions
    MATCH (t)-[:assembled_with]->(_r:revision)
    
    RETURN {
      uuid: t.uuid,
      start: t.start,
      end: t.end,
      description: t.description,
      client: CASE WHEN exists(cl.uuid) THEN {uuid: cl.uuid} ELSE NULL END,
      case: CASE WHEN exists(ca.uuid) THEN {uuid: ca.uuid} ELSE NULL END,
      step: CASE WHEN exists(s.uuid) THEN {uuid: s.uuid} ELSE NULL END,
      note: CASE WHEN exists(n.uuid) THEN {uuid: n.uuid} ELSE NULL END,
      revisions: count(_r)
    }
    
  • InverseFalcon
    InverseFalcon over 3 years
    As it's been a number of years and new options for this are available, please refer to my newer answer (referencing our knowledge base article on conditional cypher execution) instead of this answer.
  • Pygirl
    Pygirl over 3 years
    Thanks for posting out the new tricks +1. can you just tell me what's the advantage of using apoc module? Actually I am a noob. I am done with neo4j (cypher) So wanted to know whether I should use apoc for all the problems.
  • InverseFalcon
    InverseFalcon about 3 years
    APOC is a supplemental library. It offers some functionality that is currently impossible with Cypher alone. For other cases, it offers an alternate choice. You can include it and decide when and where to use its functions and procedures.