1

In cypher, there isn't an 'official' conditional update. This is emulated using a 'case trick' IF, which is a CASE inside a FOREACH as demonstrated here and here.

Here's the query I'm working with:

MATCH (reaper:REAPER)-[:TO_REAP]->(doomed)
WHERE reaper <> doomed
WITH reaper, doomed
LIMIT 1
OPTIONAL MATCH (doomed)-[x]-(doomed)
DELETE x
WITH reaper, doomed
OPTIONAL MATCH (doomed)-[r]-(related)
DELETE r, doomed
WITH related, reaper
WHERE related <> reaper
FOREACH(ignore_me IN CASE WHEN
    related IS NOT NULL
            THEN [1] ELSE [] END | MERGE (reaper)-[:TO_REAP]->(related))
RETURN 1

The purpose of this REAPER is to delete nodes (subgraphs). We do this by deleting nodes directly connected to it ('doomed' nodes), then connecting all of the children of 'doomed' nodes directly to the REAPER (related) using the MERGE. Of course, if there are no children, we don't have anything to connect, which is where the FOREACH and CASE come in. If there are no children (related), then one would expect the FOREACH to NO-OP and move on to the RETURN.

The issue is when related IS null, the query craps out, and doesn't return ANYTHING, but also DOES NOT FAIL. This is a problem, because we cannot determine when there is nothing to delete VS. a 'leaf' (single node without children) node was deleted, due to this issue.

When trying to diagnose the issue, I removed the 'if' statement (FOREACH and CASE), and when related is null, it throws an error 'Other node is null'.

I am suspecting that the FOREACH loop is somehow eating this error, even though the MERGE shouldn't be executing at all in that condition.

I'm suggesting that there's a possible neo4j/cypher bug, so I would like someone to come in and tell me how wrong and inefficient my query is XD

edit: I forgot to talk about data staging, if you wanted to test this query yourself.

This will set you up to experience my issue:

CREATE (r:REAPER)
CREATE (r)-[:TO_REAP]->(n)-[:doesntmatter]->(m)
CREATE (r)-[:TO_REAP]->(p)

When you run the delete query, you'll notice that for the 'n' node, it will return a 1. But for the other 2 nodes, m and p (which are leaf nodes), they will get deleted, but nothing will be returned.

Community
  • 1
  • 1
brehon1104
  • 53
  • 2
  • 11

1 Answers1

2

This info might help.

Cypher handles the following 2 contrived snippets differently. Let's assume that there is no Foo node.

Snippet 1. (The WHERE clause aborts the query, which returns nothing):

OPTIONAL MATCH (n:Foo)
WITH n
WHERE NULL = 123
RETURN 1;

Snippet 2. (The query completes and returns 1):

OPTIONAL MATCH (n:Foo)
WHERE NULL = 123
RETURN 1;

This difference in behavior may be what is causing your issue.

However, Cypher requires that you use a WITH to separate a DELETE from a subsequent MATCH or WHERE clause.

One way to work around this might be to just move the last WITH clause up by 2 lines (right after its corresponding OPTIONAL MATCH), so that it will not abort the query:

MATCH (reaper:REAPER)-[:TO_REAP]->(doomed)
WHERE reaper <> doomed
WITH reaper, doomed
LIMIT 1
OPTIONAL MATCH (doomed)-[x]-(doomed)
DELETE x
WITH reaper, doomed
OPTIONAL MATCH (doomed)-[r]-(related)
WHERE related <> reaper
DELETE r, doomed
WITH related, reaper
FOREACH(ignore_me IN CASE WHEN
    related IS NOT NULL
            THEN [1] ELSE [] END | MERGE (reaper)-[:TO_REAP]->(related))
RETURN 1
cybersam
  • 63,203
  • 6
  • 53
  • 76
  • Thanks cybersam! I had to modify the query slightly from what you've posted, because the 'r' that is getting deleted ALSO was deleting the original :TO_REAP relationship. When you move the where clause, that TO_REAP relationship is no longer included. So I simply had to delete it further up (where I delete the 'x'). Regardless, your diagnosis is correct. I was doing WITH on a null value, causing the query to crash. Edit: Also, it was slightly confusing, but you said to move the last WITH clause, and you meant to say last WHERE clause (it's correct in your query though). Might want to edit that – brehon1104 Apr 07 '16 at 17:53
  • Also, I don't think it's the WHERE clause on a null WITH that aborts the query. This also fails:| match (x:THIS_DOESNT_EXIST) with x return 1 | So it looks like queries abort when you WITH on a null variable – brehon1104 Apr 07 '16 at 19:15