Create node on condition - neo4j

I'm trying to make a relationship between nodes in Neo4j if a certain condition is met like. Currently I have node(a) and node(b):
What I want
if node(b) is in label1 then make relation: node(a)-[:r]-node(b:label1)
else merge node(b) in label2 then make relation node(a)-[:r]-node(b:label2)
What I have
match (a:label1 {id:"t1"})
merge (b:label1 {id:"t6"})
on create
set b:label2 remove b:label1
merge (a)-[:Friends_with]-(b)

Unfortunately, Cypher doesn't really have an if-then-else syntax (ON MATCH and ON CREATE is the closest you will get). I would recommend running running multiple cyphers and executing follow-ups based on the return result.
So like
MATCH (a:label1 {id:"t1"})
MATCH (b:label1 {id:"t6"})
MERGE (a)-[:Friends_with]-(b)
return COUNT(b) as b1Exists
And if that returns 0, then execute
MATCH (a:label1 {id:"t1"})
MERGE (b:label2 {id:"t6"})
MERGE (a)-[:Friends_with]-(b)
Depending on your data, you might get away with this
MATCH (a:label1 {id:"t1"})
MERGE (b {id:"t6"})
ON CREATE SET b:label2
MERGE (a)-[:Friends_with]-(b)
But just know that doing this in 1 Cypher will probably result in bugs (In this case, if there are more valid labels than 1 and 2. You will want a UUID for this method)

Related

Delete relationships and then add back to node neo4j

I would like to get a node, delete all outgoing relationships of a certain type and then add back relationships.
The problem I have is that once I grab the node, it still maintains it's previous relationships even after delete so instead of having 1 it keeps doubling whatever it has. 1->2->4->8 etc
Sample graph:
CREATE (a:Basic {name:'a'})
CREATE (b:Basic {name:'b'})
CREATE (c:Basic {name:'c'})
CREATE (a)-[:TO]->(b)
CREATE (a)-[:SO]->(c)
The query to delete the previous relationships and then add in the new relationships. (this is just a brief sample where in reality it wouldn't add back the same relationships, but more then likely point it to a different node).
MATCH (a:Basic {name:'a'})
WITH a
OPTIONAL MATCH (a)-[r:TO|SO]->()
DELETE r
WITH a
MATCH (b:Basic {name:'b'})
CREATE (a)-[:TO]->(b)
WITH a
MATCH (c:Basic {name:'c'})
CREATE (a)-[:SO]->(c)
If I change the CREATE to MERGE then it solves the problem, but it feels odd to have to merge when I know that I just deleted all the relationships. Is there a way to update "a" midway through the query so it reflects the changes? I would like to keep it in one query
The behavior you observed is due the subtle fact that the OPTIONAL MATCH clause generated 2 rows of data, which caused all subsequent operations to be done twice.
To force there to be only a single row of data after the DELETE clause, you can use WITH DISTINCT a (instead of WITH a) right after the DELETE clause, like this:
MATCH (a:Basic {name:'a'})
OPTIONAL MATCH (a)-[r:TO|SO]->()
DELETE r
WITH DISTINCT a
MATCH (b:Basic {name:'b'})
CREATE (a)-[:TO]->(b)
WITH a
MATCH (c:Basic {name:'c'})
CREATE (a)-[:SO]->(c)

Where vs property specification

Trying to understand when to use a property value vs a WHERE clause.
$Match (g:GROUP {GroupID: 1}) RETURN g
gives the expected response (all reported properties as expected).
And,
$match (a:ADDRESS {AddressID: 454}) return a
gives the expected response (all reported properties as expected).
However, the combo in a MERGE
MERGE (g:GROUP {GroupID: 1})-[r:USES]->(a:ADDRESS {AddressID: 454}) Return g.ShortName, type(r), a.Line1;
creates two new nodes (with no properties, of course, except a redundant AddressID and GroupID. The AddressID and GroupID were created with toInt() and I tried putting the property values in toInt() also (same result):
Added 2 labels, created 2 nodes, set 2 properties, created 1 relationship, returned 1 row in 77 ms.
So, after DETACH DELETE the extraneous nodes, I try again with (which works)
Match (g:GROUP) WHERE g.GroupID = 1
Match (a:ADDRESS) WHERE a.AddressID = 454
MERGE (g)-[r:USES]->(a)
RETURN g.ShortName, type(r), a.Line1
Returned 1 row in 14 ms.
WHY does the separate MATCHing work while the property spec does not?
MERGE is one of the trickier clauses for exactly this behavior.
From the Cypher documentation for MERGE:
When using MERGE on full patterns, the behavior is that either the
whole pattern matches, or the whole pattern is created. MERGE will not
partially use existing patterns — it’s all or nothing. If partial
matches are needed, this can be accomplished by splitting a pattern up
into multiple MERGE clauses.
So when you're going to MERGE a pattern, and you aren't using variables bound to already existing nodes, then the entire pattern is matched, or if it doesn't exist, the entire pattern is created, which, in your case, creates duplicate nodes, as your intent is to use existing nodes in the MERGE.
In general, when you want to MERGE a relationship or pattern between nodes that already exist, it's best to MATCH or MERGE on the nodes which should already exist first, and then MERGE the pattern with the matched or merged variables.
EDIT
I think there's some confusion here about the reasons for the differences in the queries.
This doesn't have anything to do with whether the properties are defined in a WHERE clause, or inline on the nodes in the MATCH clauses.
In fact, you can do this just fine with your last query, and it will behave identically:
Match (g:GROUP {GroupID:1})
Match (a:ADDRESS {AddressID:454})
MERGE (g)-[r:USES]->(a)
RETURN g.ShortName, type(r), a.Line1
The reasons for the differences, again, the behavior of MERGE
Really the easiest way to grasp what's going on is to consider what the behavior would be if MERGE were substituted first with MATCH, and then if no match was found, with CREATE.
MATCH (g)-[r:USES]->(a)
and if there is no match, it does CREATE instead
CREATE (g)-[r:USES]->(a)
That should make sense...a CREATE with existing nodes will create the missing part, the relationship.
Contrast that with using MERGE on the entire pattern:
MERGE (g:GROUP {GroupID: 1})-[r:USES]->(a:ADDRESS {AddressID: 454})
Return g.ShortName, type(r), a.Line1;
First this will attempt a MATCH:
MATCH (g:GROUP {GroupID: 1})-[r:USES]->(a:ADDRESS {AddressID: 454})
and then when no match is found, a CREATE
CREATE (g:GROUP {GroupID: 1})-[r:USES]->(a:ADDRESS {AddressID: 454})
And given what we know of how CREATE works, it doesn't attempt to match parts of the pattern (and there are no variables that have already matched to existing elements of the graph), it creates the pattern as a whole, creating a brand new :GROUP and :ADDRESS node with the given properties, and the new :USES relationship.
MERGE (g:GROUP {GroupID: 1})-[r:USES]->(a:ADDRESS {AddressID: 454}) Return g.ShortName, type(r), a.Line1; most likely creates two nodes, because those properties (GroupID for the GROUP node / AddressID for ADDRESS node) are not the only properties on those nodes.
Matching the nodes first ensures that you're getting nodes with matching properties (which could have other properties, too) and merge those.
If you had an index with uniqueness constraint on both GroupID for GROUP nodes and AddressID for ADDRESS nodes, then the MERGE without matching first, should still make that connection.

How to delete and merge outgoing relationships on the same query

I am using cypher. I am trying to delete all out going relationships before creating new ones on the same query.
i have weird situation if the relations/nodes already existed it's working as expected. if They never been created before I get:
(no changes, no rows)
This is my query:
match (user{userId:'a'})-[r:nearby_wifi]->() delete r
MERGE (p1:BT{userId:'a'}) WITH p1, [{bssid:"0a:18:d6:c1:3d:fd",level:"-51",timestamp:"1973-08-27 02:26:35.423",venueName:""},{bssid:"04:18:d6:c2:3e:2a",level:"-55",timestamp:"1973-08-27 02:26:35.425",venueName:""},{bssid:"0e:18:d6:c1:3d:fd",level:"-53",timestamp:"1973-08-25 11:06:07.392",venueName:""}] AS wifis
UNWIND wifis AS wifi
MERGE (p2:WIFI{bssid: wifi.bssid})
MERGE (p1)-[r1:nearby_wifi]->(p2)
SET r1.dist=wifi.dist
SET p1.lastTimeActive=1460378030215
SET p2.level=wifi.level
SET p2.timestamp=wifi.timestamp
SET p2.venueName=wifi.venueName
Any idea why when combining delete and the merge executions I got no changes(when graph empty)?
Thanks.
Replace first match with optional match
For example if you have no client nodes in your database, but have some person nodes query
Match (p:Client) with p Match (r:Person) return *
will get nothing, but query
Optional Match (p:Client) with p Match (r:Person) return *
will give you Persons. I think neo4j optimizer stops executing query after it gets no results and with optional match it gets null, and continues executing.

MATCH prior to MERGE in single Cypher query confuses ON MATCH and ON CREATE

I've run into this on Neo4j 2.1.5. I have a query which I'm issuing from Node.js using the Neo4j REST API. The point of this query is to be able to create or update a given Node and set its state (including labels and properties) to some known state. The MATCH and REMOVE clause prior to the WITH is to work around the fact that there's no direct way to remove all of a Node's labels nor is there a way to update a Node's labels with a given set of labels. You have to explicitly remove the labels you don't want and add the labels you do want. And there's no way to remove labels in the MERGE clause.
A somewhat simplified version of the query looks like:
MATCH (m {name:'Brian'})
REMOVE m:l1:l2
WITH m
MERGE (n {name:'Brian'})
ON MATCH SET n={mprops} ON CREATE SET n={cprops}
RETURN n
where mprops = {updated:true, created:false} and cprops = {updated:false, created:true}. I do this so that in a single Cypher query I can remove all of the Node's existing labels and set new labels using the ON MATCH clause. The problem is that including the initial MATCH seems to confuse the ON MATCH vs ON CREATE logic.
Assuming the Brian Node already exists, the result of this query should show that n.created = false and n.updated = true. However, I get the opposite result, n.created=true, n.updated=false. If I remove the initial MATCH (and WITH) clause and execute only the MERGE clause, the results are as expected. So somehow, the inclusion of the MATCH clause causes the MERGE clause to think that a CREATE vs MATCH is happening.
I realize this is a weird use of the WITH clause, but it did seem like it would work around the limitation in manipulating labels. And Cypher thinks that it's valid Cypher. I'm assuming this is just a bug and an edge case, but I wanted to get others insights and possible alternatives before I report it.
I realize that I could have created a transaction and issued the MATCH and MERGE as separate queries within that transaction, but there are reasons that this does not work well in the design of the API I'm writing.
Thanks!
If you prefix your query with MATCH it will never execute if there is no existing ('Brian') node.
You also override all properties with your SET n = {param} you should use SET n += {param}
MERGE (n:Label { name:'Brian' })
ON MATCH SET n += {create :false,update:true }
ON CREATE SET n += {create :true,update:false }
REMOVE n:WrongLabel
RETURN n
I don't see why your query would not work, but the issues brought up by #FrobberOfBits are valid.
However, logically, your example query is equivalent to this one:
MATCH (m {name:'Brian'})
REMOVE m:l1:l2
SET m={mprops}
RETURN m
This query is simpler, avoids the use of MERGE entirely, and may avoid whatever issue you are seeing. Does this represent what you were trying to do?

Neo4J Cypher - Case Insensitive MERGE

Is there a way to do a case insensitive MERGE in Cypher (Neo4J)?
I'm creating a graph of entities I have been able to extract from a set of documents, and want to merge entities that are the same across multiple documents (accepting the risk that the same name doesn't mean it's the same entity!). The issue is that the case can vary between documents.
At the moment, I'm using the MERGE syntax to create merged nodes, but it is sensitive to the differences in case. How can I perform a case-insensitive merge?
There is no direct way but you can try out something like below.MERGE is made for pattern matching and labels of different cases constitute different patterns
MERGE (a:Crew123)
WITH a,labels(a) AS t
LIMIT 1
MATCH (n)
WHERE [l IN labels(n)
WHERE lower(l)=lower(t[0])] AND a <> n
WITH a,collect(n) AS s
FOREACH (x IN s |
DELETE a)
RETURN *
The above query will give you an ERROR but it will delete the newly created node if a similar label exists. You can add additional pattern in the MERGE clause . And in case there are no similar labels it will run successfully.
Again this is just a work around to not allow new similar labels.
If the data is coming for instance from a CSV or similar source (parameter) you can use a dedicated, consistent case property for the merge and set the original value separately.
e.g.
CREATE CONSTRAINT ON (u:User) ASSERT u.iname IS UNIQUE;
LOAD CSV WITH HEADERS FROM "http://some/url" AS line
WITH line, lower(line.name) as iname
MERGE (u:User {iname:iname}) ON CREATE SET u.name = line.name;
The best solution we found was to change our schema to include a labelled node that contains the upper-cased value which we can merge on, whilst still retaining the case information on the original mode. E.g. (OriginalCase)-[uppercased]->(ORIGINALCASE)

Resources