Cypher How to use variables in a query - neo4j

So, I have a tree of Person and I'm trying to query a subtree of a given node in the tree (root) and limit the subtree levels returned (max):
with "A" as root, 3 as max
match (a:Person {name: root})-[:PARENT*1..max]->(c:Person)
return a, c
But it's giving me this error:
Invalid input 'max': expected "$", "]", "{" or <UNSIGNED_DECIMAL_INTEGER> (line 2, column 44 (offset: 71))
"match (a:Person {name: root})-[:PARENT*1..max]->(c:Person)"
Both root and max will be an input, so in the code I've tried parameterizing those values:
result = tx.run(
"""
match (a:Person {name: $root})-[:PARENT*1..$max]->(c:Person)
return a, c
""",
{"root": "A", "max": 2}
)
but:
code: Neo.ClientError.Statement.SyntaxError} {message: Parameter maps cannot be used in MATCH patterns (use a literal map instead, eg. "{id: {param}.id}
(Based on #nimrod serok's answer)
I guess, I can just sanitize the max and manually interpolate it into the query string. But I'm wondering if there's a cleaner way to do it.

One way to insert the parameters in the code is:
result = tx.run(
'''
match (a:Person {name:''' + root +'''})-[:PARENT*1..''' + max + ''']->(c:Person)
return a, c
'''
)
or use the equivalent query, but with format:
result = tx.run(
'''
match (a:Person)-[:PARENT*1..{}]->(c:Person)
WHERE a.name={}
return a, c
'''.format(max, root)
)
If you want to use the UI instead , you can use:
:param max =>2
As can be seen here

One of the simplest ways is just to pass the values as strings in the query. You can achieve this by using the template literal.
e.g.
result = tx.run(
`
match (a:Person {name:'${root}'})-[:PARENT*1..'${max}']->(c:Person)
return a, c
`)

Related

Accessing nodes in path in neo4j without apoc

Can anyone tell me if this is possible? When trying to use
Unwind apoc.coll.pairsMin(nodes(p)) as pair
It throws
Neo.ClientError.Statement.SyntaxError: Unknown function 'apoc.coll.pairsMin' (line 3, column 8 (offset: 99))
"Unwind apoc.coll.pairsMin(nodes(p)) as pair"
If possible I would prefer to find a solution using out of the box software
[UPDATED]
This snippet should work:
WITH NODES(p) AS ns
UNWIND [i IN RANGE(0,SIZE(ns)-2) | ns[i..i+2]] AS pair
Something like this work for you?
// find the path you are interested in
MATCH p=(:Node {name: 'start'})-[*]->(:Node {name: 'end'})
// use reduce to iterate over the relationships
// accumulate the collections of the start and endNode for each relation
RETURN REDUCE (pairs = [], rel in relationships(p) | pairs + [[startNode(rel), endNode(rel)]] ) AS pairs
The equivalent APOC call would look like this
MATCH p=(:Node {name: 'start'})-[*]->(:Node {name: 'end'})
RETURN apoc.coll.pairsMin(nodes(p)) as pairs

Cypher - how to walk graph while computing

I'm just starting studying Cypher here..
How would would I specify a Cypher query to return the node connected, from 1 to 3 hops away of the initial node, which has the highest average of weights in the path?
Example
Graph is:
(I know I'm not using the Cypher's notation here..)
A-[2]-B-[4]-C
A-[3.5]-D
It would return D, because 3.5 > (2+4)/2
And with Graph:
A-[2]-B-[4]-C
A-[3.5]-D
A-[2]-B-[4]-C-[20]-E
A-[2]-B-[4]-C-[20]-E-[80]-F
It would return E, because (2+4+20)/3 > 3.5
and F is more than 3 hops away
One way to write the query, which has the benefit of being easy to read, is
MATCH p=(A {name: 'A'})-[*1..3]-(x)
UNWIND [r IN relationships(p) | r.weight] AS weight
RETURN x.name, avg(weight) AS avgWeight
ORDER BY avgWeight DESC
LIMIT 1
Here we extract the weights in the path into a list, and unwind that list. Try inserting a RETURN there to see what the results look like at that point. Because we unwind we can use the avg() aggregation function. By returning not only the avg(weight), but also the name of the last path node, the aggregation will be grouped by that node name. If you don't want to return the weight, only the node name, then change RETURN to WITH in the query, and add another return clause which only returns the node name.
You can also add something like [n IN nodes(p) | n.name] AS nodesInPath to the return statement to see what the path looks like. I created an example graph based on your question with below query with nodes named A, B, C etc.
CREATE (A {name: 'A'}),
(B {name: 'B'}),
(C {name: 'C'}),
(D {name: 'D'}),
(E {name: 'E'}),
(F {name: 'F'}),
(A)-[:R {weight: 2}]->(B),
(B)-[:R {weight: 4}]->(C),
(A)-[:R {weight: 3.5}]->(D),
(C)-[:R {weight: 20}]->(E),
(E)-[:R {weight: 80}]->(F)
1) To select the possible paths with length from one to three - use match with variable length relationships:
MATCH p = (A)-[*1..3]->(T)
2) And then use the reduce function to calculate the average weight. And then sorting and limits to get one value:
MATCH p = (A)-[*1..3]->(T)
WITH p, T,
reduce(s=0, r in rels(p) | s + r.weight)/length(p) AS weight
RETURN T ORDER BY weight DESC LIMIT 1

Neo4j Cypher match doesn't find node that it should

Having added nodes with properties "id" and "name"
CREATE (s:subsystem {id: 12, name:"InjectEolCurrentCellSOCs"})
CREATE (s:subsystem {id: 13, name:"InjectEolCellCapacities"})
CREATE (s:subsystem {id: 14, name:"InjectEolCellResistances"})
CREATE (s:subsystem {id: 15, name:"InjectEolCellSOCs"})
This command works/finds the node and returns the requested value:
match(n {id:13}) return (n.name);
But this command does not find a match:
match(n {name:"InjectEolCellCapacities"}) return (n);
Could this be related to the fact that "InjectEolCellCapacities" and "InjectEolCellResistances" have the same first 13 characters ?
If you look at your first imag, you will see that you have saved the data "InjectEolCellCapacities " (there is a space at the end).
So if you want to match it, yu should use this query : MATCH (n:subsystem { name:"InjectEolCellCapacities "}) RETURN n
You can also search all subsystem node that have a name property that starts with InjectEolCellCapacities like this : MATCH (n:subsystem) WHERE n.name STARTS WITH 'InjectEolCellCapacities' RETURN n

Neo4j is there a way to create a variable number of relationships in one cypher query?

I would like to create N nodes with a sequential relationship between each of them.
Think of my requirement as creating a workflow for a user. On the UI end it can send an array of json objects that must relate to each other sequentially. For example:
{steps: [ {name: 'step 1'}, {name: 'step2'}, {name: 'step3'}] }
What I want from the above json is to create 3 nodes and have them sequentially linked
(step 1)-[:has_next_step]->(step 2)-[:has_next_step]->(step 3)
Is there a quick way of doing this? Keep in mind my example has 3 nodes, but in reality I may have anywhere from 5-15 steps so the cypher query must be able to handle this variable input. Note that I have control over the input as well so if there is an easier json params variable I can use that as well.
You can, the only issue you will face is when iterating the collection of steps you will not be able to recognise the node representing the element before in the collection.
So a bit of hacking, you can use a timestamp in the beginning of the query to act as identifier :
WITH {steps: [ {name: 'step 1'}, {name: 'step2'}, {name: 'step3'}] } AS object
WITH object.steps AS steps, timestamp() AS identifier
UNWIND range(1, size(steps)-1) AS i
MERGE (s:Step {id: identifier + "_" + (i-1)}) SET s.name = (steps[i-1]).name
MERGE (s2:Step {id: identifier + "_" + (i)}) SET s2.name = (steps[i]).name
MERGE (s)-[:NEXT]->(s2)
Explanation :
I iterate the collection of steps with UNWIND, in order to recognize each node representing an already iterated step, I use a dummy identifier being the timestamp of the transaction + "_" + the sequence cursor.
At large scale you would better use your own identifiers (like a generated uuid on client side) and have an index/unique constraint on it.
More Advanced :
You have a User node and want to attach steps to it (context : the user didn't had any steps connected to it before)
Create a dummy user:
CREATE (u:User {login:"me"})
Create steps list and attach to user
WITH {steps: [ {name: 'step 1'}, {name: 'step2'}, {name: 'step3'}] } AS object
WITH object.steps AS steps, timestamp() AS identifier
UNWIND range(1, size(steps)-1) AS i
MERGE (s:Step {id: identifier + "_" + (i-1)}) SET s.name = (steps[i-1]).name
MERGE (s2:Step {id: identifier + "_" + (i)}) SET s2.name = (steps[i]).name
MERGE (s)-[:NEXT]->(s2)
WITH identifier + "_" + (size(steps)-1) AS lastStepId, identifier + "_0" AS firstStepId
MATCH (user:User {login:"me"})
OPTIONAL MATCH (user)-[r:LAST_STEP]->(oldStep)
DELETE r
WITH firstStepId, lastStepId, oldStep, user
MATCH (s:Step {id: firstStepId})
MATCH (s2:Step {id: lastStepId})
MERGE (user)-[:LAST_STEP]->(s)
WITH s2, collect(oldStep) AS old
FOREACH (x IN old | MERGE (s2)-[:NEXT]->(x))
Context, (run the same query but with different names for steps to visually see the diff) : The user has already steps attached to him :
WITH {steps: [ {name: 'second 1'}, {name: 'second 2'}, {name: 'second 3'}] } AS object
WITH object.steps AS steps, timestamp() AS identifier
UNWIND range(1, size(steps)-1) AS i
MERGE (s:Step {id: identifier + "_" + (i-1)}) SET s.name = (steps[i-1]).name
MERGE (s2:Step {id: identifier + "_" + (i)}) SET s2.name = (steps[i]).name
MERGE (s)-[:NEXT]->(s2)
WITH identifier + "_" + (size(steps)-1) AS lastStepId, identifier + "_0" AS firstStepId
MATCH (user:User {login:"me"})
OPTIONAL MATCH (user)-[r:LAST_STEP]->(oldStep)
DELETE r
WITH firstStepId, lastStepId, oldStep, user
MATCH (s:Step {id: firstStepId})
MATCH (s2:Step {id: lastStepId})
MERGE (user)-[:LAST_STEP]->(s)
WITH s2, collect(oldStep) AS old
FOREACH (x IN old | MERGE (s2)-[:NEXT]->(x))
You can use a couple of APOC procedures to create the nodes and then link them together:
apoc.create.nodes can be used to create multiple nodes with the same label(s).
apoc.nodes.link can be used to chain the nodes together with relationships of the same type.
For example, the query below will create your 3 sample nodes (with a Step label) and then link them together, in order, with has_next_step relationships:
CALL apoc.create.nodes(['Step'],[{name:'step1'},{name:'step2'},{name: 'step3'}]) YIELD node
WITH COLLECT(node) AS nodes
CALL apoc.nodes.link(nodes, 'has_next_step')
RETURN SIZE(nodes)
The apoc.nodes.link procedure does not return anything, so the above query just returns the number of nodes that were created and linked together.

Remove all labels for Neo4j Node

The following examples are taken from the Neo4j documentation found here.
Using Cypher, it is possible to remove a single, known label using a Cypher statement like so:
MATCH (n { name: 'Peter' })
REMOVE n:German
RETURN n
You can also remove multiple labels like so:
MATCH (n { name: 'Peter' })
REMOVE n:German:Swedish
RETURN n
So how would one remove all labels from a node using simple Cypher statements?
You can also try this way using doIt method from apoc library:
match (n {name: 'Peter'})
call apoc.cypher.doIt(
"match (o)" +
" where ID(o) = " + ID(n) +
" remove "+reduce(a="o",b in labels(n) | a+":"+b) +
" return (o);",
null)
yield value
return value
There's no syntax for that yet! Labels are usually things that are known quantities, so you can list them all out if you want. There's no dynamic way to remove them all, though.
so, how about a two step cypher approach? use cypher to generate some cypher statements and then execute your cypher statements in the shell.
You could try something like this to generate the batch cypher statements
match (n)
return distinct "match (n"
+ reduce( lbl_str= "", l in labels(n) | lbl_str + ":" + l)
+ ") remove n"
+ reduce( lbl_str= "", l in labels(n) | lbl_str + ":" + l)
+ ";"
The output should look something like this...
match (n:Label_1:Label_2) remove n:Label_1:Label_2;
match (n:Label_1:Label_3) remove n:Label_1:Label_3;
match (n:Label_2:Label_4) remove n:Label_2:Label_4;
You would probably want to remove any duplicates and depending on your data there could be quite a few.
Not exactly what you are looking for but I think it would get you to the same end state using just cypher and the neo4j shell.
Shiny NEW and improved cypher below...
I edited this down to something that would work in the browser alone. It hink this is a much better solution. It is still two steps but it produces a single statement that can be cut and paste into the browser.
match (n)
with distinct labels(n) as Labels
with reduce(lbl_str="", l in Labels | lbl_str + ":" + l) as Labels
order by Labels
with collect(Labels) as Labels
with Labels, range(0,length(Labels) - 1) as idx
unwind idx as i
return "match (n" + toString(i) + Labels[i] + ")" as Statement
union
match (n)
with distinct labels(n) as Labels
with reduce(lbl_str="", l in Labels | lbl_str + ":" + l) as Labels
order by Labels
with collect(Labels) as Labels
with Labels, range(0,length(Labels) - 1) as idx
unwind idx as i
return "remove n" + toString(i) + Labels[i] as Statement
which produces output like this...
match (n0:Label_A)
match (n1:Label_B)
match (n2:Label_C:Label_D)
match (n3:Label_E)
remove n0:Label_A
remove n1:Label_B
remove n2:Label_C:Label_D
remove n3:Label_E
which can then be cut and paste into the Neo4j browser.

Resources