Bossie
Bossie

Reputation: 3612

Neo4j: implementing soft delete with optional relationships

I'm trying to implement a soft delete in Neo4j. The graph described in Cypher from Alice's viewpoint is as such:

(clyde:User)<-[:FOLLOWS]-(alice:User)-[:LIKES]->(bob:User)

Instead of actually deleting a node and its relationships, I'm

  1. changing its label so it can no longer be looked up directly, i.e. dropping its User label and adding a _User label (notice the underscore)
  2. replacing its relationships so it can't be reached anymore by my normal queries, e.g. deleting its :FOLLOWS relationships and replacing it with :_FOLLOWS relationships.

So this is basically the equivalent of moving a row to an archiving table in a relational database. I figured this is a pretty efficient approach because you're effectively never visiting the parts of the graph that have been soft-deleted. Also, you don't have to modify any of your existing queries.

The result of soft-deleting Alice should be this:

(clyde:User)<-[:_FOLLOWS]-(alice:_User)-[:_LIKES]->(bob:User)

My first attempt at the query was this:

match (user:User {Id: 1})
optional match (user)-[follows:FOLLOWS]->(subject)
remove user:User set user:_User
delete follows
create (user)-[:_FOLLOWS]->(subject);

The problem is that when this user is not following anyone, the query tries to create a relationship between user and null because the second match is optional, so it gives me this error: Other node is null.

My second attempt was this:

match (user:User {Id: 1})
remove user:User set user:_User
optional match (user)-[follows:FOLLOWS]->(subject)
foreach (f in filter(f in collect({r: follows, n: subject}) where f.r is not null) | delete f.r create (user)-[:_FOLLOWS]->(f.n));

So I'm putting the relationship and the subject into a map, collecting these maps in a collection, throwing every "empty" map away and looping over the collection. But this query gives me this error:

SyntaxException: Invalid input '.': expected an identifier character, node labels, a property map, whitespace or ')' (line 1, column 238)

Does anyone know how I can fix this?

Thanks, Jan

Upvotes: 3

Views: 1547

Answers (1)

jjaderberg
jjaderberg

Reputation: 9952

Could you change the label first and then match for relationships? Then you should be able to use 'non-optional' match, and not have to deal with the cases where there are no follows relationships, something like

MATCH (user:User {Id: 1})
REMOVE user:User SET user:_User
WITH user
MATCH (user)-[follows:FOLLOWS]->(subject)
DELETE follows
CREATE (user)-[:_FOLLOWS]->(subject)

Or you could carry the user, follows and subject and filter on where subject is not null. Something like

MATCH (user:User {Id: 1})
OPTIONAL MATCH (user)-[follows:FOLLOWS]->(subject)
REMOVE user:User SET user:_User
WITH user, follows, subject
WHERE subject IS NOT NULL
DELETE follows
CREATE (user)-[:_FOLLOWS]->(subject)

Edit:
If the problem is that you want to do this for more than one kind of relationship, then you could try

MATCH (user:User {Id: 1})
REMOVE user:User SET user:_User
WITH user 
MATCH (user)-[f:FOLLOWS]->(other)
DELETE f 
CREATE (user)-[:_FOLLOWS]->(other)
WITH user LIMIT 1 
MATCH (user)-[l:LIKES]->(other)
DELETE l 
CREATE user-[:_LIKES]->(other)

You can keep extending it with other relationship types, just be sure to limit user when you carry, since multiple matches (user)-[r]->(other) means there are multiple results for user, or you'll run the next query part multiple times.

I don't think there is a generic way to do it in cypher since you can't dynamically build the relationship type (i.e. CREATE (a)-[newRel:"_"+type(oldRel)]->(b) doesn't work)

Is something like that what you are looking for or am I misunderstanding your question?

Upvotes: 3

Related Questions