Add value to array if its not already in there - neo4j

I want to add a value to an array if its not already there.
So far my code looks something like this (note that both r.names and {name} are arrays, and [1] + [2] = [1,2]):
MERGE (r:resource {hash:{hash}})
ON CREATE SET r.names = {name}
ON MATCH SET r.names = r.names + {name}
but obviously if {name} is already in r.names, it just gets added again. How can I add {name} only if r.names doesn't already contain it?

I guess you need to use the FOREACH + CASE WHEN trick: using a case when you use either a 1 element array (if your condition is true) or a 0 element array otherwise as iterator used in FOREACH. FOREACH cannot be used in a ON MATCH or ON CREATE handler, so we put it after the MERGE and use a coalesce to cover the case when r.names does not yet exist:
MERGE (r:Resource {hash:{hash}})
FOREACH(x in CASE WHEN {name} in r.names THEN [] ELSE [1] END |
SET r.names = coalesce(r.names,[]) + {name}
)
RETURN r.names

Use FILTER to get an array of the elements that aren't already in r.names and add them:
MERGE (r:resource {hash:{hash}})
ON CREATE SET r.names = {new_names}
ON MATCH SET r.names = r.names + FILTER(
el FOR el in {new_names} IF NOT el IN r.names
)

Related

How to Set and Update a List Property in Neo4J?

I'd like to know if it's possible to update a list property of a node or edge by index.
MATCH (t1:t)-[r1:o]->(a:a)<-[r2:o]-(t2:t) where r1.loc-r2.loc=-1 and r1.month=r2.month
WITH t1,t2,count(t1) as c
MERGE (t1)-[r:r]->(t2)
ON CREATE SET r.weights = "empty array of size 12(months) with array[month]=c
ON MATCH SET r.weights[month] = r.weights[month]+c
I suppose one can achieve setting the empty array with something similar to:
ON CREATE SET r.weights = reduce(a=[], i in range(0,month-1) | a + [0])+[c]+reduce(a=[], i in range(0,12-month) | a + [0])
But what's the best way to update the list property?
Thanks!
I managed to solve it in the following way:
MATCH (t1:t)-[r1:o]->(a1:a)<-[r2:o]-(t2:t) where r1.loc-r2.loc=-1 and r1.month=r2.month
WITH t1,r1,t2,r2,count(t1) as c
MERGE (t1)-[r:r]->(t2)
ON CREATE SET r.weights = reduce(a=[], i in range(0,r1.month-2) | a + [0])+[c]+reduce(a=[], i in range(0,11-r1.month) | a + [0])
ON MATCH SET r.weights = reduce(a=[], e in r.weights[0..r1.month-1]+[r.weights[r1.month-1]+c]+r.weights[r1.month..13] |a+e)
return t1,t2,c,r

Neo4j Creating a relationship conditionally based on one of the property of node

I need to create two different type of relationship between two nodes. Type of relationship depends upon one of the property of the node.
For Example,
I have two nodes USER and EVENT. I have two relationships to create between them.
1. invite
2. requestToInvite
Even node has property inviteOnly.
Create a "Invite" relationship if inviteOnly is true. Otherwise create "requestToInvite" relationship.
This is what i am trying:
MATCH (u:User)
WHERE ID(u) = 13
WITH u
MATCH (e:Events)
WHERE ID(e) = 0
WITH u,e
CREATE (u)-[:inviteONLYTrue]->(e) WHERE e.inviteOnly = true
CREATE (u)-[:inviteONLYFALSE]->(e) WHERE e.inviteOnly = false
WITH u,e
RETURN u,e
Currently there is no conditional but you can work around it by iterating over a zero or one-element list which is created by a CASE statement.
MATCH (u:User) WHERE ID(u) = 13
MATCH (e:Events) WHERE ID(e) = 0
FOREACH (_ in case e.inviteOnly when true then [1] else [] end |
CREATE (u)-[:inviteONLYTrue]->(e) )
FOREACH (_ in case e.inviteOnly when false then [1] else [] end |
CREATE (u)-[:inviteONLYFALSE]->(e) )
RETURN u,e
APOC Procedures just updated with support for conditional cypher execution, but in this particular case all you'll need is a way to create a relationship with a dynamic relationship type. APOC has a procedure for this too. Here's an example:
MATCH (u:User)
WHERE ID(u) = 13
WITH u
MATCH (e:Events)
WHERE ID(e) = 0
WITH u,e, CASE WHEN e.inviteOnly THEN 'inviteONLYTrue' ELSE 'inviteONLYFALSE' END as relType
CALL apoc.create.relationship(u, relType, {}, e) YIELD rel
RETURN u,e

Neo4J - Return everything from node but property key

I have generalized cypher that returns different types of nodes whose property names may vary from which I need to exclude any property name called "password". I've tried using a CASE statement inside of EXTRACT, FILTER and REDUCE but I am not getting anywhere.
RETURN reduce(props = {}, x IN keys(stateNode) |
case when x <> "password" then
props + {x: stateNode[x]}
else
props + {"password": "changed"}
end
)
This obviously won't work but I am not sure how else to do it. I don't have the luxury of knowing what the other property names are so I cant explicitly return every property excluding "password" property.
If it is OK to return an array of arrays (where each inner array contains a key/value pair), then you can use something like this:
RETURN REDUCE(s = [], x IN keys(stateNode) |
s + [[x, (CASE WHEN x = "password" THEN "changed" ELSE stateNode[x] END)]]
) AS props;
The result would look something like:
[["name","Fred"],["age",22],["password","changed"]]

Change a property with condition

The query shall check wether a relation is already existing and in that case should check if a property has a special value and change it depending on the check. If that relation doesn exist it shall be created.
I have tried several ways and what comes closest is
MERGE (u:User {uuid: {userUUID}}) -[r:relation {rType: {rType}}]-> (n:Node)
ON CREATE SET
r.uuid = {relUUID},
r.status = {relStatus}
ON MATCH SET
r.status = {relStatus}
WHERE r.status = "1" // Only if r.status of the existing pattern is 1 it shall be changed to the value of relStatus
RETURN r
The WHERE isnt correct syntax - maybe someone has a hint how I can check a property and only change the property based on a special trigger when using ON MATCH.
Thanks.
This should work:
MERGE (u:User {uuid: {userUUID}}) -[r:relation {rType: {rType}}]-> (n:Node)
ON CREATE SET
r.uuid = {relUUID},
r.status = {relStatus}
ON MATCH SET
r.status = CASE WHEN r.status = "1" THEN {relStatus} ELSE r.status END
RETURN r;

Set/Change property with dynamic name using Cypher

So when I try to set up a property name/value pair, I'm not sure what the property name will be. It will depend on a meta data node.
Two metadata nodes:
{ id:1, value:'name' }
{ id:2, value:'age' }
I need to add a property for a Person node but I don't want to use condition statement (and cypher does not have condition statement). I want something like:
if (metadata.id = 1) {
set person.name = 'xx'
} else if (metadata.id = 2) {
set person.age = 'xx'
}
or:
match (m:metadata{id:1}), (p:person{id:1}) set p.'m.value' = 'xx'
I don't want to use the if / else condition statement. Is there a cypher condition statement, or how can this be achieved?
You can use WHERE for filtering on conditions
MATCH (p:Person) WHERE p.age > 21 SET p.drunk = true
or you can use a CASE expression to generate a zero or one-element list that you then can use with FOREACH
MATCH (p:Person)
WITH p, (case when p.age > 21 then [1] else [] end) as drunk_filter
FOREACH (x in drunk_filter | SET p.drunk = true)
Foreach has the advantage that the query continues after that.

Resources