Reputation: 3612
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
User
label and adding a _User
label (notice the underscore):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
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