2

I have a list of posts that belong to a user. The posts are structured as linked list that belongs to a given user.

I am having deleting the a given node while preserving the data structure.

Here is an illustration of the structure.

enter image description here

In my list attempt I have written the query below, however it does not work since the MATCH does not return anything in the sub-queries.

I thought about using OPTIONAL MATCH in the sub-queries, however that results in exceptions.

MATCH(node:Post)
WHERE ID(node) = 2749
WITH node
MATCH(user:User)-[:OWNS]->(node)-[:PREV]->(nextNode:Post)
CREATE(user)-[:OWNS]->(nextNode)
WITH node
MATCH(prevNode:Post)-[:PREV]->(node)-[:PREV]->(nextNode:Post)
CREATE(prevNode)-[:PREV]->(nextNode)
WITH node
DETACH DELETE node

I assumed that if a pattern in a sub-query fails than the rest of the sub-query will be skipped and the next sub-query will still execute. But it seems that the whole thing does not get executed.

How do we perform something like in the following in sub-queries without halting the execution.

IF pattern matches
THEN 
   CREATE EDGE 
IF pattern matches
THEN
   Do something else

Solution

stdob--'s answer is the closest to the right solution, however it fails to delete nodes at the end of the list and throws an exception as it does not check if the nextNode exists in the second FOREACH statement.

It needed tweaking to work:

MATCH(node:Post) WHERE ID(node) = 2813

OPTIONAL MATCH (user:User)-[:OWNS]->(node)
OPTIONAL MATCH (node)-[:PREV]->(nextNode:Post)
OPTIONAL MATCH (prevNode:Post)-[:PREV]->(node)

FOREACH (ith in CASE 
    WHEN user IS NOT NULL AND nextNode IS NOT NULL 
    THEN [1] 
    ELSE [] 
    END | CREATE (user)-[:OWNS]->(nextNode) 
)

FOREACH (ith in CASE WHEN 
    prevNode IS NOT NULL AND nextNode IS NOT NULL 
    THEN [1] ELSE [] 
    END | CREATE (prevNode)-[:PREV]->(nextNode)
)

DETACH DELETE node
thedethfox
  • 1,651
  • 2
  • 20
  • 38
  • Implementing `IF pattern matches THEN CREATE EDGE` is quite straightforward, you just use something like: `MATCH CREATE (n1)-[:EDGE_TYPE]->(n2)`, where `n1` and `n2` are nodes in the pattern. – Gabor Szarnyas Dec 30 '17 at 01:24

2 Answers2

2

You can use combination of OPTIONAL MATCH, FOREACH and CASE:

MATCH(node:Post) WHERE ID(node) = 2749

OPTIONAL MATCH (user:User)-[:OWNS]->(node)
OPTIONAL MATCH (node)-[:PREV]->(nextNode:Post)
OPTIONAL MATCH (prevNode:Post)-[:PREV]->(node)

FOREACH (ith in CASE WHEN 
                     NOT user IS NULL AND 
                     NOT nextNode IS NULL 
                THEN [1] ELSE [] END |
    CREATE (user)-[:OWNS]->(nextNode) 
)

FOREACH (ith in CASE WHEN 
                     NOT prevNode IS NULL AND
                     NOT nextNode IS NULL
                THEN [1] ELSE [] END |
    CREATE (prevNode)-[:PREV]->(nextNode)
)

DETACH DELETE node

Update: Also you can use the procedure apoc.do.when from the apoc.library for the conditional query execution.

stdob--
  • 28,222
  • 5
  • 58
  • 73
  • This fails to delete the last node. I have tweaked it and now it works. Thanks a lot. I have added the tweaked answer at the end of the question. If you care to modify your answer to check that `nextNode` exists in the second foreach, then I will accept your answer. Again thanks a lot, I just learned about pipes in neo4j :-) – thedethfox Dec 30 '17 at 10:47
  • Yes, you're right, I added a fix and a small add-on. – stdob-- Dec 30 '17 at 13:13
1

I recreated your data set with the following query:

CREATE
  (u:User {name: 'Alice'})-[:OWNS]->
  (p1:Post {number: 1})-[:PREV]->
  (p2:Post {number: 2})-[:PREV]->
  (p3:Post {number: 3})-[:PREV]->
  (p4:Post {number: 4})-[:PREV]->
  (p5:Post)

As you mentioned, using OPTIONAL MATCH can result in exceptions, something along the lines of:

Expected to find a node at prevNode but found nothing Some(NO_VALUE)

However, you can work around this, by first doing the DELETE, then passing on the value using a WITH clause and only running the CREATE if the variable introduced by the OPTIONAL MATCH clause IS NOT NULL.

MATCH (user:User)-[:OWNS]->(node)-[:PREV]->(nextNode:Post)
WHERE node.number = 1
CREATE (user)-[:OWNS]->(nextNode)
WITH node, nextNode
OPTIONAL MATCH (prevNode:Post)-[:PREV]->(node)
DETACH DELETE node
WITH prevNode, nextNode
WHERE prevNode IS NOT NULL
CREATE (prevNode)-[:PREV]->(nextNode)
Gabor Szarnyas
  • 4,410
  • 3
  • 18
  • 42
  • What if you try to delete a node in the middle of the list, for example, at number 3? – stdob-- Dec 30 '17 at 01:54
  • Wow, well spotted. I missed passing `nextNode` along. I tested it again and it seems to be working fine now. – Gabor Szarnyas Dec 30 '17 at 02:24
  • This fails to delete nodes in the middle and end of the list. The reason being that first MATCH statement will always fail as it assumes that the node to be deleted is always first node. Notice that it always has an edge to the user node. A node at the end or the middle cannot have and edge having an :OWNS label. Thumbs up for the try though. – thedethfox Dec 30 '17 at 10:51