Cypher Query Neo4j ForEach Merge return? - neo4j

So I'm trying to send a query that will Merge a number of nodes and I want the query to return the nodes that it created but I can't put the return statement inside the FOREACH so is there a way to collect the nodes and then return that collection at the end?
FOREACH (tagName in {tags} |
MERGE (n:items {classid:tagName.pClassid})
ON CREATE
COLLECT(n) as allCreatedNodes)
RETURN allCreatedNodes;
"params" : {
"tags": [{"pClassid" : 1}, {"pClassid" : 2}, {"pClassid" : 3}]
}

Right now that's unfortunately not possible.
The only thing that you can do (if you really need it is to look the nodes up after the fact. And unfortunately using IN with a MATCH is not optimized yet.
FOREACH (tagName in {tags} | MERGE (n:items {classid:tagName.pClassid}))
WITH [t IN {tags} | t.pClassid ] as classIds
MATCH (allCreatedNodes:items)
WHERE allCreatedNodes.classid IN classIds
RETURN allCreatedNodes;

One thing you could try is creating a temporary graph structure to quickly look up the nodes after foreach. It probably won't be worth it if the tag collection is small, but it's sometimes a useful strategy and until label indices handle IN collection style lookups it may be worth a shot. Basically it means maintaining some small graph structure as a transient (query-local or query-type-local) index. In this case,
begin the query by creating (query-local) or merging (query-type-local) an index node
relate all the merged nodes to it
and when its time to return match from index node to the created nodes
delete the relationships (and index node if query-local) and return.
I can't test this at the moment, but you could try something like this
CREATE (index)
FOREACH (tagName in {tags} |
MERGE (n:items {classid:tagName.pClassid} )
CREATE index-[:TRANSIENT]->n
)
WITH index
MATCH index-[t:TRANSIENT]->n
DELETE t, index
RETURN n
(This may be overkill for your type of query. If you try it, do try profiling the query in comparison with a re-fetch from label index and post back.)

Related

Update nodes by a list of ids and values in one cypher query

I've got a list of id's and a list of values. I want to catch each node with the id and set a property by the value.
With just one Node that is super basic:
MATCH (n) WHERE n.id='node1' SET n.name='value1'
But i have a list of id's ['node1', 'node2', 'node3'] and same amount of values ['value1', 'value2', 'value3'] (For simplicity i used a pattern but values and id's vary a lot). My first approach was to use the query above and just call the database each time. But nowadays this isn't appropriate since i got thousand of id's which would result in thousand of requests.
I came up with this approach that I iterate over each entry in both lists and set the values. The first node from the node list has to get the first value from the value list and so on.
MATCH (n) WHERE n.id IN["node1", "node2"]
WITH n, COLLECT(n) as nodeList, COLLECT(["value1","value2"]) as valueList
UNWIND nodeList as nodes
UNWIND valueList as values
FOREACH (index IN RANGE(0, size(nodeList)) | SET nodes.name=values[index])
RETURN nodes, values
The problem with this query is that every node gets the same value (the last of the value list). The reason is in the last part SET nodes.name=values[index] I can't use the index on the left side nodes[index].name - doesn't work and the database throws error if i would do so. I tried to do it with the nodeList, node and n. Nothing worked out well. I'm not sure if this is the right way to achieve the goal maybe there is a more elegant way.
Create pairs from the ids and values first, then use UNWIND and simple MATCH .. SET query:
// THe first line will likely come from parameters instead
WITH ['node1', 'node2', 'node3'] AS ids,['value1', 'value2', 'value3'] AS values
WITH [i in range(0, size(ids)) | {id:ids[i], value:values[i]}] as pairs
UNWIND pairs AS pair
MATCH (n:Node) WHERE n.id = pair.id
SET n.value = pair.value
The line
WITH [i in range(0, size(ids)) | {id:ids[i], value:values[i]}] as pairs
combines two concepts - list comprehensions and maps. Using the list comprehension (with omitted WHERE clause) it converts list of indexes into a list of maps with id,value keys.

cypher - relationship traversal vs ordering by property

graph snippet
I have a Shape with a list of Points.
I have the following requirements:
1) retrieve an ordered set of Points;
2) insert/remove a Point and preserve the order of the rest of the Points
I can achieve this by:
A) Point has a sequence integer property that could be used to order;
B) Add a :NEXT relationship between each Point to create a linked list.
I'm new to Neo4j so not sure which approach is preferable to satisfy the requirements?
For the first requirement, I wrote the following queries and found the performance for the traversal to be poor but Im sure its a badly constructed query:
//A) 146 ms
Match (s:Shape {id: "1-700-y11-1.1.I"})-[:POINTS]->(p:Point)
return p
order by p.sequence;
//B) Timeout! Bad query I know, but dont know the right way to go about it!
Match path=(s:Shape {id: "1-700-y11-1.1.I"})-[:POINTS]->(p1:Point)-[:NEXT*]->(p2:Point)
return collect(p1, p2);
To get ordered list of points use a slightly modified version of the second query:
Match path=(s:Shape {id: "1-700-y11-1.1.I"})-[:POINTS]->(P1:Point)
-[:NEXT*]-> (P2:Point)
WHERE NOT (:Point)-[:NEXT]->(P1) AND
NOT (P2)-[:NEXT]->(:Point)
RETURN TAIL( NODES( path) )
And for example query to delete:
WITH "id" as pointToDelete
MATCH (P:Point {id: pointToDelete})
OPTIONAL MATCH (Prev:Point)-[:NEXT]->(P)
OPTIONAL MATCH (P)-[:NEXT]->(Next:Point)
FOREACH (x in CASE WHEN Prev IS NOT NULL THEN [1] ELSE [] END |
MERGE (Prev)-[:NEXT]->(Next)
)
DETACH DELETE P

Getting endNodes from a list of matched nodes

Starting with Neo4j, I am pretty sure, this question could be even stupid because the solution might be so easy. But I am not sure how to solve this, I want to combine two queries:
First I search for nodes, which are all themselves parent nodes:
MATCH (parentnodes:DemoMenue) WHERE (parentnodes)-[:IS_PARENT_OF]-> () RETURN parentnodes
Then I want do get the endNodes for each of the matched parentnodes above. If just doing it with one specified parentnode it would for example work like this:
MATCH (x:DemoMenue {title: "A2"})-[r]-() RETURN endNode(r)
Now what is the suggested way/technique in Cypher to apply my second query to each of the matching parentnodes of my first query?
Your two queries can be merged into one without using a foreach loop:
MATCH (parentnodes:DemoMenue)-[r:IS_PARENT_OF]-> () RETURN endNode(r)
If you want to do something for each result in a collection, you can use Foreach, but keep in mind that you can only use SET in foreach, not return.
And to complete my answer, your query is not 'returning' any collection, so you can't use foreach on it, you'll have to use collect before
Why don't you just use:
MATCH (parentnodes:DemoMenue)-[:IS_PARENT_OF]-> (endNode)
RETURN endNodes

Keep track of Changes in Neo4j - Achieve functionality like a "flag" variable in standard programming

Initial setup of the sample database is provided link to console
There are various cases and within each case, there are performers(with properties id and name). This is the continuation of problems defined problem statement and solution to unique node creation
The solution in the second link is (credits to Christophe Willemsen
)
MATCH (n:Performer)
WITH collect(DISTINCT (n.name)) AS names
UNWIND names as name
MERGE (nn:NewUniqueNode {name:name})
WITH names
MATCH (c:Case)
MATCH (p1)-[r:RELATES_TO]->(p2)<-[:RELATES]-(c)-[:RELATES]->(p1)
WITH r
ORDER BY r.length
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
SET rf.strength = CASE WHEN rf.strength IS NULL THEN r.value ELSE rf.strength + r.value END
This solution achieved what was asked for.
But I need to achieve something like this.
foreach (Case.id in the database)
{
foreach(distinct value of r.Length)
{
//update value property of node normal
normal.value=normal.value+0.5^(r.Length-2)
//create new nodes and add the result as their relationship or merge it to existing one
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
//
rf.strength=rf.strength + r.value*0.5^(r.Length-2);
}
}
The problem is to track the change in the case and then the r.Length property. How can it be achieved in Cypher?
I will not redo the last part, where setting strengths.
One thing though, in your console link, there is only one Normal node, so why do you need to iterate over each case, you can just match distinct relationships lengths.
By the way for the first part :
MATCH (n:Case)
MATCH (n)-[:RELATES]->()-[r:RELATES_TO]->()<-[:RELATES]-(n)
WITH collect(DISTINCT (r.length)) AS lengths
MATCH (normal:Normal)
UNWIND lengths AS l
SET normal.value = normal.value +(0.5^l)
RETURN normal.value
Explanations :
Match the cases
Foreach case, match the RELATES_TO relationships of Performers for that Case
collect distinct lengths
Match the Normal node, iterate the the distinct lengths collection and setting the proper value on the normal node

Neo4J Arrays in MATCH query

The intention of my Query is to mark similar words.
CREATE CONSTRAINT ON (n:Word) ASSERT n.title IS UNIQUE
MATCH (n) WHERE ID(n)={id}
MERGE (o:Word{title:{title}})
WITH n,o MERGE n-[r:SIMILAR{location:'{location}'}]->o
RETURN ID(o)
n is a existing Word. I want to create the relationsship & the other Word (o) if they don't exist yet.
The Problem with this query is, that it works for one title, but if I use a Array with titles the title of the Word o is the whole Array.
Can you suggest me another Query that does the same and/or a way to pass multiple values to title.
I'm using the Neography Gem on Rails e.g. the REST API
To use individual values in a parameter array you can use FOREACH, something like
MATCH (n)
WHERE ID (n) = {id}
FOREACH (t IN {title} |
MERGE (o:Word {title:t})
MERGE n-[:SIMILAR]->o
)
If you want to pass location also as a parameter (it is actually a string literal in your current query), such that merge operations for n should happen for each title, location pair in a parameter array, you can try
FOREACH (map IN {maps} |
MERGE (o:Word {title:map.title})
MERGE n-[:SIMILAR {location:map.location}]->o
)
with a parameter that looks something like
{
"maps": [
{
"title":"neography",
"location":"1.."
},{
"title":"coreography",
"location":"3.."
}
]
}
Other suggestions:
It's usually not great to look up nodes by internal id from parameter. In some cases when chaining queries it may be fine, but in most cases label index lookup would be better: MATCH (n:Word {title:"geography"})
If you are not using the transactional cypher endpoint, give it a shot. You can then make one or more calls with one or more queries in each call within one transaction. Performance improves and you may find you don't need to send the more complex parameter object, but can send many simple queries.

Resources