I have a tree-like graph as shown below
Now let's say I start from the root node R and want to find all the paths from 1 to the nearest type B node. In the example graph, the result should be
path-1: 1,2
path-2: 1,3,6,10,13
path-3: 1,3,7,10,13
How can I do this?
Keep the node type in the label - (:A) and (:B), relationships between nodes are of type 'connect'.
// Find all paths from Root to all B-nodes
MATCH (A:A {name:1}), p = (A)-[:connect*]->(B:B)
// Get all node labels for each path
WITH A, p, extract( n in nodes(p) | labels(n) ) as pathLabels
// We find the number of occurrences of B-node in each path
WITH A, p, reduce( bCount = 0, Labels in pathLabels |
CASE WHEN 'B' IN Labels THEN 1 ELSE 0 END + bCount
) as bCount
// Return only the path in which the B-node is in the end of the path
WHERE bCount = 1
RETURN p
Example data query:
MERGE (A1:A {name:1})-[:connect]-(B2:B {name:2}) MERGE (A1)-[:connect]-(A3:A {name:3}) MERGE (B2)-[:connect]-(A4:A {name:4}) MERGE (B2)-[:connect]-(A5:A {name:5}) MERGE (A4)-[:connect]-(B8:B {name:8}) MERGE (B8)-[:connect]-(A11:A {name:11}) MERGE (B8)-[:connect]-(A12:A {name:12}) MERGE (A5)-[:connect]-(A9:A {name:9}) MERGE (A3)-[:connect]-(A6:A {name:6}) MERGE (A3)-[:connect]-(A7:A {name:7}) MERGE (A6)-[:connect]-(A10:A {name:10}) MERGE (A7)-[:connect]-(A10) MERGE (A10)-[:connect]-(B13:B {name:13}) RETURN *
Update (searching not A-type nodes):
// Find all paths from Root to all not A-nodes
MATCH (A:A {name:1}), p = (A)-[:connect*]->(B) WHERE NOT 'A' IN labels(B)
// Get all node labels for each path
WITH A, p, extract( n in nodes(p) | labels(n) ) as pathLabels
// We find the number of occurrences of A-node in each path
WITH A, p, reduce( aCount = 0, Labels in pathLabels |
CASE WHEN 'A' IN Labels THEN 1 ELSE 0 END + aCount
) as aCount
// Return only the path in which the count of A-node
// is 1 less the total number of nodes in the path.
WHERE aCount = length(p)
RETURN p
Related
Say I have the following graph:
(a1:A) -> (b1:B) -> (c1:C) -> (d1:D)
\ /
- -> (x1:X) - --> (y1:Y)
(a2:A) -> (b2:B) -> (c2:C) -> (d2:D)
(a3:A) -> (x3:X) -> (y3:Y) -> (d3:D)
The actual graph also contains other relationships between node label A and D. But I am only interested in these relationships between A and D. So I have to force some rules on the path. see query below
match p1=((:A)-->(:B)-->(:C)->(:D))
return p1
match p2=((:A)-->(:X)-->(:Y)->(:D))
return p2
This will return me four rows
a1-b1-c1-d1
a1-x1-y1-d1
a2-b2-c2-d2
a3-x3-y3-d3
But I would like to return an array of subgraphs and merge paths based on the common node attribute D.name. I.e., d1, d2, d3 are differentiated by their node attribute called "name". So the output I would like to have is
// query logic
// return subgraphs, each subgraph is a row. each subgraph contains the unique node and relationships from A to D. If there are multiple paths to the same D, then merge these paths to the same subgraph
More concretely, the return of the above example becomes
row1: nodes(a1, b1, c1, d1, x1, y1), relationships(a1, b1, c1, d1, x1, y1)
row2: nodes(a2, b2, c2, d2), relationships(a2, b2, c2, d2)
row3: nodes(a3, x3, y3, d3), relationships(a3, x3, y3, d3)
What would be the query?
UPDATE:
Test graph
merge (a1:A{name: 'a1'})
merge (b1:B{name: 'b1'})
merge (c1:C{name: 'c1'})
merge (d1:D{name: 'd1'})
merge (x1:X{name: 'x1'})
merge (y1:Y{name: 'y1'})
merge (a2:A{name: 'a2'})
merge (b2:B{name: 'b2'})
merge (c2:C{name: 'c2'})
merge (d2:D{name: 'd2'})
merge (a3:A{name: 'a3'})
merge (x3:X{name: 'x3'})
merge (y3:Y{name: 'y3'})
merge (d3:D{name: 'd3'})
merge(a1)-[:TESTS]->(b1)
merge(b1)-[:TESTS]->(c1)
merge(c1)-[:TESTS]->(d1)
merge(a1)-[:TESTS]->(x1)
merge(x1)-[:TESTS]->(y1)
merge(y1)-[:TESTS]->(d1)
merge(a2)-[:TESTS]->(b2)
merge(b2)-[:TESTS]->(c2)
merge(c2)-[:TESTS]->(d2)
merge(a3)-[:TESTS]->(x3)
merge(x3)-[:TESTS]->(y3)
merge(y3)-[:TESTS]->(d3)
It seems that not just the :D nodes are different, but also the :A nodes. But, assuming that you want group by d.name, I guess this does it.
MATCH p=((:A)-[*]->(d:D))
RETURN d.name AS dName,
apoc.coll.toSet(
apoc.coll.flatten(
COLLECT(nodes(p))
)
) AS nodes,
apoc.coll.toSet(
apoc.coll.flatten(
COLLECT(relationships(p))
)
) AS relationships
In case you want to filter for specific paths, you can do something like:
MATCH (a:A), (d:D)
OPTIONAL MATCH p1=((a)-->(:B)-->(:C)-->(d))
OPTIONAL MATCH p2=((a)-->(:X)-->(:Y)-->(d))
WITH d.name AS dName,
apoc.coll.toSet(
apoc.coll.flatten(
COLLECT(COALESCE(nodes(p1),[]) + COALESCE(nodes(p2),[]))
)
) AS nodes,
apoc.coll.toSet(
apoc.coll.flatten(
COLLECT(COALESCE(relationships(p1),[]) + COALESCE(relationships(p2),[]))
)
) AS relationships
WHERE nodes <> []
RETURN nodes, relationships
The scenario is the following:
I have a set of nodes of type x that are linked to nodes of type y.
I want to match all x nodes except those that are linked to a y node that has an attribute equal to a particular value.
Example input:
CREATE (a:x {name: 'a'}), (b:x {name: 'b'}), (c:x {name: 'c'});
CREATE (d:y {name: 'd', attrib: 1}), (e:y {name: 'e', attrib: 2}),
(f:y {name: 'f', attrib: 3}), (g:y {name: 'g', attrib: 4}),
(h:y {name: 'h', attrib: 5}), (i:y {name: 'i', attrib: 6});
MATCH (a), (d), (e) WHERE a.name = 'a' AND d.name = 'd' AND e.name = 'e'
CREATE (a)-[r:z]->(d), (a)-[s:z]->(e) RETURN *;
MATCH (b), (f), (g) WHERE b.name = 'b' AND f.name = 'f' AND g.name = 'g'
CREATE (b)-[r:z]->(f), (b)-[s:z]->(g) RETURN *;
MATCH (c), (h), (i) WHERE c.name = 'c' AND h.name = 'h' AND i.name = 'i'
CREATE (c)-[r:z]->(h), (c)-[s:z]->(i) RETURN *;
Here I want to return all the x nodes except those that are linked to a y node that has attrib = 5.
Here's what I tried:
MATCH (n:x)-[]-(m:y) WHERE NOT m.attrib = 5 RETURN n
From this query I get all x nodes, that is: a, b and c. I would like to exclude c, because it's linked to h, which has h.attrib = 5.
Edit:
I found a query that does the job:
MATCH (n:x), (m:x)-[]-(o:y)
WHERE o.attrib = 5
WITH collect(n) as all_x_nodes, collect(m) as bad_x_nodes
RETURN [n IN all_x_nodes WHERE NOT n IN bad_x_nodes]
The problem is that it's not efficient. Any better alternative?
This simple query should do exactly what you asked for: "return all the x nodes except those that are linked to a y node that has attrib = 5."
MATCH (n:x)
WHERE NOT (n)--(:y {attrib: 5})
RETURN n;
A better approach is to find all :x nodes that you want to exclude (that are connected to the :y node with the specific attribute), collect those x nodes, then match to all :x nodes that aren't in the collection:
MATCH (exclude:x)--(:y{attrib:5})
WITH collect(distinct exclude) as excluded
MATCH (n:x)
WHERE NOT n in excluded
RETURN collect(n) as result
An alternate approach using APOC Procedures is to get both collections, and subtract the excluded collection from the other:
MATCH (exclude:x)--(:y{attrib:5})
WITH collect(distinct exclude) as excluded
MATCH (n:x)
WITH excluded, collect(n) as nodes
RETURN apoc.coll.subtract(nodes, excluded) as result
In either case, it would help to have an index on :y(attrib). In this data set it doesn't matter. On much larger sets it will.
How could I imply restrictions on variable length path?
I have all possible paths from some start node query:
CREATE INDEX ON :NODE(id)
MATCH all_paths_from_Start = (start:Person)-[:FRIENDSHIP*1..20]->(person:Person)
WHERE start.id = 128 AND start.country <> "Uganda"
RETURN paths;
No I want filter out all paths which have at least two persons with the same country. How could I do that?
1) Get an array of countries to the path with possible duplicates: REDUCE
2) Remove duplicates and compare the sizes of arrays: UNWIND + COLLECT(DISTINCT...)
MATCH path = (start:Person)-[:FRIENDSHIP*1..20]->(person:Person)
WHERE start.id = 128 AND start.country <> "Uganda"
WITH path,
REDUCE(acc=[], n IN NODES(path) | acc + n.country) AS countries
UNWIND countries AS country
WITH path,
countries, COLLECT(DISTINCT country) AS distinctCountries
WHERE SIZE(countries) = SIZE(distinctCountries)
RETURN path
P.S. REDUCE can be replaced by EXTRACT (thanks to Gabor Szarnyas):
MATCH path = (start:Person)-[:FRIENDSHIP*1..20]->(person:Person)
WHERE start.id = 128 AND start.country <> "Uganda"
WITH path,
EXTRACT(n IN NODES(path) | n.country) AS countries
UNWIND countries AS country
WITH path,
countries, COLLECT(DISTINCT country) AS distinctCountries
WHERE SIZE(countries) = SIZE(distinctCountries)
RETURN path
P.P.S. Thanks again to Gabor Szarnyas for another idea for simplifying the query:
MATCH path = (start:Person)-[:FRIENDSHIP*1..20]->(person:Person)
WHERE start.id = 128 AND start.country <> "Uganda"
WITH path
UNWIND NODES(path) AS person
WITH path,
COLLECT(DISTINCT person.country) as distinctCountries
WHERE LENGTH(path) + 1 = SIZE(distinctCountries)
RETURN path
One solution that I can think of is to get the nodes of the path, and for each person on the path, extract the value of the number of persons from the same country (which we determine by filtering for the same country. A path has persons from unique countries if it has zero persons from the same country, i.e. for all persons, there is only a single person (the person himself/herself) from that country.
MATCH p = (start:Person {id: 128})-[:FRIENDSHIP*1..20]->(person:Person)
WHERE start.country <> "Uganda"
WITH p, nodes(p) AS persons
WITH p, extract(p1 IN persons | size(filter(p2 IN persons WHERE p1.country = p2.country))) AS personsFromSameCountry
WHERE length(filter(p3 IN personsFromSameCountry WHERE p3 > 1)) = 0
RETURN p
The query is syntactically correct but I didn't test it on any data.
Note that I moved the id = 128 condition to the pattern and shortened the all_paths_from_Start variable as p.
Let's say, I have a path A->B->C->D and the relationships have a property val.
Now, I have to pick any two nodes from the path and if the rel.val>0.8
and if it is true for all the pair of nodes, then return the path
Ex:
P = A-->B-->C-->D
All nodes = [A,B,C,D]
return p if{
rel.val of (A,B) >0.8
rel.val of (A,C) >0.8
rel.val of (A,D) >0.8
rel.val of (B,C) >0.8
rel.val of (B,D) >0.8
rel.val of (C,D) >0.8
}
Here is my query, (of course the query is wrong):
MATCH p=(a{word:"quality"})-[r*1..2]->(b)
WHERE NONE (n IN nodes(p) WHERE size(filter(x IN nodes(p) WHERE n = x))> 1)
MATCH q = (a)-[r:coocr]->(b) where a in nodes(p) AND b in nodes(p) AND NOT b = a AND None(rel IN rels(q) WHERE rel.val < 0.8 )
RETURN p
In summary, you want to MATCH a path and then make sure that all pairs of nodes in your path are connected by a relationship which fullfills a certain criterion (rel.val > 0.8).
Interesting question, I think this is not really straightforward. Maybe I am overlooking something obvious?
Here is an idea how to approach the problem. You first MATCH your path, then MATCH between all nodes in the path and count the number of relationships with rel.val > 0.8. This number has to be the size of the factorial of the number of nodes (num relationships == (num nodes)!, number of possible combinations of 2).
The following query returns the number of relationships, but I don't know how to compare this to the factorial of the number of nodes:
// match your path like before
MATCH p=(a:Uselabel {word:"quality"})-[r:USETYPE*1..2]->(b)
// use unwind to get the nodes from the path
UNWIND nodes(path) AS x
// do this twice to match the nodes onto themselves
UNWIND nodes(path) AS y
// match your relationship
MATCH (x)-[rel:USETYPE]-(y)
// criterion for your relationship
WHERE rel.val > 0.8
// only if two different nodes
WHERE x <> y
// get the count of pairs
WITH p, count(DISTINCT rel) AS num_pairs
// now I don't know how to get/compare the factorial of the number of nodes :)
RETURN num_pairs
I didn't find a built-in function for the factorial, so you have to look into this.
I want to find all paths given a start node
MATCH path=(n)-[rels*1..10]-(m)
with the following 2 conditions on path inlcusion:
true if relationship between subsequent nodes in path has property PROP='true'
if type(relationship)=SENDS then true if direction of the relationship is outgoing (from one path node to the next node in the path)
Another way of phrasing this is that direction doesn't matter unless the relationship name is SENDS
I can do condition 1 with WHERE ALL (r IN rels WHERE r.PROP='true') however ive no idea how to do condition 2.
The only way I can think of to filter on relationship direction without declaring direction in the match pattern is by comparing the start node of each relationship in the path with the node at the corresponding index of the nodes() collection from the path. For this you need the relationship and node collections from the path, an index counter and some boolean evaluation equivalent to ALL(). One way to do it is to use REDUCE with a collection for the accumulator, so you can accumulate index and maintain a true/false value for the path at the same time. Here's an example, the accumulator starts at [0,1] where the 0 is the index for testing that startNode(r) equals the node at the corresponding index in the node collection (i.e. it's an outgoing relationship) and the 1 represents true, which signifies that the path has not yet failed your conditions. For each relationship the index value is incremented and the CASE/WHEN clause multiplies the 'boolean' with 1 if your conditions are satisfied, with 0 if not. The evaluation of the path is then the evaluation of the second value in the collection returned by REDUCE -- if 1 then yay, if 0 then boo.
MATCH path = (n)-[*1..10]-(m)
WITH path, nodes(path) as ns, relationships(path) as rs
WHERE REDUCE(acc = [0,1], r IN rs |
[acc[0]+1, CASE WHEN
r.PROP='true' AND
(type(r) <> "SENDS" OR startNode(r) = ns[acc[0]]) THEN acc[1]*1 ELSE acc[1]*0 END]
)[1] = 1
RETURN path
or maybe this is more readable
WHERE REDUCE(acc = [0,1], r IN rs |
CASE WHEN
r.PROP=true AND
(type(r) <> "SENDS" OR startNode(r) = ns[acc[0]])
THEN [acc[0]+1, acc[1]*1]
ELSE [acc[0]+1, acc[1]*0]
END
)[1] = 1
Here's a console: http://console.neo4j.org/?id=v3kgz9
For completeness I've answered the question using jjaderberg correct solution plus a condition to fix the start node and ensure that no zero length paths are included
MATCH p = (n)-[*1..10]-(m)
WHERE ALL(n in nodes(p) WHERE 1=length(filter(m in nodes(p) WHERE m=n)))
AND (id(n)=1)
WITH p, nodes(p) as ns, relationships(p) as rs
WHERE REDUCE(acc = [0,1], r IN rs | [acc[0]+1,
CASE WHEN r.PROP='true' AND (type(r) <> "SEND" OR startNode(r) = ns[acc[0]])
THEN acc[1]*1
ELSE acc[1]*0
END])[1] = 1
RETURN nodes(p);
Or my alternative answer based on jjaderbags answer but does not use accumulator but which is slightly slower
MATCH p=(n)-[rels*1..10]-(m)
WHERE ALL(n in nodes(p) WHERE 1=length(filter(m in nodes(p) WHERE m=n)))
AND( ALL (r IN rels WHERE r.PROP='true')
AND id(n)=1)
WITH p, range(0,length(p)-1) AS idx, nodes(p) as ns, relationships(p) as rs
WHERE ALL (i in idx WHERE
CASE type(rs[i])='SEND'
WHEN TRUE THEN startnode(rs[i])=ns[i]
ELSE TRUE
END)
RETURN nodes(p);