I am trying to create/update distinct relations between two nodes with a single bulk operation in Cypher, leveraging the MERGE and FOREACH clause.
Right now, I am trying to do it with the following, but it is not syntactically correct:
MERGE (u1:Person {id:1})
MERGE (u2:Person {id:3})
FOREACH (score IN [{name:'R1',val:1.0},{name:'R2',val:0.5}]|
MERGE (u1)-[r]-(u2)
WHERE type(r) = score.name
ON CREATE SET r.weight=score.val,r.created=timestamp(),r.updated=r.created
ON MATCH SET r.weight=score.val,r.updated=timestamp()
)
May you please suggest me a query to achieve that.
I think the problem with your query is this:
MERGE (u1)-[r]-(u2)
WHERE type(r) = score.name
Creting relationships without a type is not allowed, nor is it to use a variable name (score.name) for the type of the relationship. I can only suggest two partial solutions:
1) If you are writing the query from some code, insert the name from there. For instance, in PHP: :
....
$rels[] = [val => 1.0, name => 'R1'];
foreach ($rels as $rel) {
$query[] = 'MERGE (u1)-[r:' . '$rel["name"]' . ']-(u2)';
ON CREATE SET r.weight=score.val, r.created=timestamp(), r.updated=r.created
ON MATCH SET r.weight=score.val,r.updated=timestamp()
}
....
This probably would give an error because of reusing the "r" identifier for the relationship, but that could be avoided making it variable too.
2) A cleaner solution, but maybe not available in your environment, is to use APOC. In Neo4j 3.0+ it´s available to install with many functions for you to use, in particular apoc.create.relationship. I am not familiar with this, but here it´s quite well explained.
Anyway here I also leave the current open issue at neo4j repository in case it´s useful.
Related
I have the following Neo4J DB model:
I need to be able to find a node of the type Concept called "idea" made by the user infranodus and see which nodes of the type Context it is connected to.
I use the following query, but it's taking too long and doesn't even load...
MATCH (ctx:Context)-[:BY]->(u:User{name:'infranodus'})
WITH DISTINCT ctx
MATCH (c:Concept{name:'idea'})-[:AT]->(ctx)<-[:AT]-(cn:Concept)
RETURN DISINCT c, ctx, count(cn);
How to fix it?
adding indexes to the neo4j database would be helpful ( i didnt see any there ) then start your match with the indexed node and traverse from there. i think that having a
MATCH (c:Concept{name:'idea'})-[:AT]->(ctx)<-[:AT]-(cn:Concept)
WHERE (ctx)-[:BY]->(u:User{name:'infranodus'})
....
would also avoid querying twice.
Create an index on the name property of nodes with the User label - a unique index would be great for this.
An index for the name property on concept would be good, too (probably without uniqueness constraint - even though you're looking for distinct nodes explicitly).
Start your query from there.
MATCH (u:User {name:'infranodus'})
MATCH (c:Concept {name: 'idea'})
MATCH (u)<-[:BY]-(ctx:Context)<-[:AT]-(c)
MATCH (c:Concept{name:'idea'})-[:AT]->(ctx)<-[:AT]-(cn:Concept)
RETURN c, ctx, size((c)-[:AT]->(ctx)<-[:AT]-(:Concept))
Doesn't look as nice, but you're asking for query tuning. Make sure to PROFILE your existing query and this. Plus, you had a typo in your query after your return keyword.
Is there any way to copy or move a relationship from one node to another?
I have a situation similar to that here:
neo4j merge 2 or multiple duplicate nodes
and here:
Copy relationships of different type using Cypher
Say I have this pattern in the graph
(a)-[r:FOO]->(b)
(a)<-[r2:BAR]-(c)
I then have another node, (d), which may or may not be a duplicate of (a). My thinking is that it does not matter whether the nodes are duplicate or not from a functionality point of view. I want to be able to move or copy the relationships r:FOO and r2:BAR so that the graph now includes
(d)-[r:FOO]->(b)
(d)<-[r2:BAR]-(c)
If I was then doing this to merge nodes when I have duplicates I would like to be able to move the relationships as opposed to copy and then (perhaps optionally) delete (a). Note that there is more than one relationship type and I do not know for sure what the types will be. I realise I can do this in stages but thought it would be great if there was an efficient way to do it in one cypher query. My current strategy is something like (not exact syntax but just to give an idea)
// get all outgoing relationships
MATCH (a:Label1 { title : 'blah' })-[r]->(o)
RETURN r
// returns FOO and BAR
// for each relationship type, create one from (d) and copy the properties over
MATCH (a:Label1 { title : 'blah' })-[r:FOO]->(o), (d:Label1 { title : 'blah blah' })
CREATE (d)-[r2:FOO]->(o)
SET r2 = r
...etc...
// now do the same for incoming relationships
MATCH (a:Label1 { title : 'blah' })<-[r]-(o)
RETURN r
// returns FOO and BAR
// for each relationship type, create one from (d) and copy the properties over
MATCH (a:Label1 { title : 'blah' })<-[r:FOO]-(o), (d:Label1 { title : 'blah blah' })
CREATE (d)<-[r2:FOO]-(o)
SET r2 = r
...etc...
// finally delete node and relationships (if required)
MATCH (a:Label1 { title : 'blah' })-[r]-(o)
DELETE r, a
However this relies on a number of queries and hence transactions. It would be much preferable (in my simpleton view) to be able to achieve this in one query. However, I do not believe anything like this exists in cypher. Am I wrong?
Any ideas? Let me know if this is not clear and I shall try to elaborate and explain further.
For info, I am using Neo4j 2.1.6 community edition (with neo4jclient from a .NET application).
Just realised that I would also have to repeat my process to account for the direction of the relationship unless I am mistaken? i.e. get all outgoing relationships from (a), recreate them as outgoing from (d), and then do the same for all incoming relationships. Cypher above has been edited accordingly.
UPDATE: I am guessing this is a pipe dream and not at all possible. Can anyone confirm this? It would be good to have a definitive answer even if it is "No!". If so I would consider asking the Neo4j guys if this functionality is even feasible and worth considering.
UPDATE 2: From the lack of ideas I am guessing this can't be done. I certainly have got no further in my research or experimentation. Looks like a feature request is the way to go. I can't be the only person who would find this functionality exceptionally useful.
I think you can just chain these together:
// get all relationships
MATCH
(a:Label1 { title : 'blah' })-[r]-(o),
(d:Label1 { title : 'blah blah' })
CREATE (d)-[r2:type(r)]-(o)
DELETE r, a
The only thing I'm not entirely sure about is the ability to use the type() function where it's being used there. I'll try it out now
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?
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)
I am feeding my neo4j db manually using cypher, so prone to error like creating duplicate nodes:
The duplicate nodes will have each relationships to other nodes.
Is there a built-in function to merge these nodes? Or should I do it manually?
Sounds possible, but complicated with cypher script:
Get the relationships of each duplicate node
Recreate them (with their properties) with the correct node (given node id)
Remove relationships to the duplicate nodes
and finally remove the duplicate nodes.
To avoid this situation in the future, please look at the MERGE keyword in Cypher.
Unfortunately, as far as I know, there is nothing in Cypher (yet) like:
MATCH (n:MyNode),(m:MyNode)
WHERE ID(n) <> ID(m) AND
PROPS(n) IN PROPS(m) AND PROPS(m) IN PROPS(n)
(...) DELETE (...)
The fictional function PROPS of the third line is not part of Cypher language and User-Defined functions have not made it yet into Neo4j.
If you're not working with production instances, the easiest is probably to back up your data folder and try to start the insertion over (with MERGE).
Otherwise, you can also try writing a traversal to collect the duplicates and delete them in batch (here is an example with the REST API).
Try this:
MATCH (n:MyNode),(m:MyNode),(o:OtherNode {id:123})
WHERE n <> m
MATCH (m)-[r:FOO]->()
CREATE (n)-[r2:FOO]->(o)
SET r2 = r
DELETE r,m
I think you can try:
apoc.refactor.mergeNodes(nodes, options)
For relations:
apoc.refactor.mergeRelationships(rels, options)
Or:
apoc.periodic.iterate(query, options)