Recursive multiple relationships - recursion

I'm attempting to recursively perform alternate match statements with 2 specific relationships.
For example, Pets are owned by a Person. Pets LIKE other people (not owner) Those people have pets owned by them, who like other people etc.
match (n.Person {id.123})<-[r.OwnedBy]-(p.Pet) Return n, r, p
match (p.Pet {id.123})-[r.Likes]->(n.Person) Return p, r, n
Notice the directional relationships involved - #1 is backwards, #2 is forwards.
What I want to do is to, given a person(id),
1. Display pets [OwnedBy] this person(id)
2. Display people [Liked] by those pets
3. Display pets [OwnedBy] the people in 2.
etc. recursively
Independently, these Match statements work. together, they do not.
I tried adding the 2nd match statement, using different variables, then it will go down 2 levels and stop.
In the real data set, there are dozens of nodes and relationships. I'm trying to limit the display to a 'tree' view of only these 2 relationships/nodes.
Thanks!

How about this?
match (n:Person {id:123})<-[:OwnedBy]-(p:Pet)-[:Likes]->(n2:Person)<-[:OwnedBy]-(p2:Pet)
return n, collect(distinct p) as pets, collect(distinct n2) as peopleLiked, collect(distinct p2) as petsOfPeopleLiked
Though if you're only interested in the graph display, this should work:
match path = (n:Person {id:123})<-[:OwnedBy]-(p:Pet)-[:Likes]->(n2:Person)<-[:OwnedBy]-(p2:Pet)
return path, n, p, n2, p2
You can also utilize APOC Procedures. This can handle showing these paths, using only these two types of relationships:
match (n:Person {id:123})
call apoc.path.expandConfig(n, {relationshipFilter:'<OwnedBy|Likes>'}) yield path
return path

Related

Creating edges in neo4j based on query results

I'm modelling a search term transition graph in a e-commerce software as a graph of nodes (terms) and edges (transitions). If a user types e.g. iphone in the search bar and then refines the query to iphone 6s this will be modeled as two nodes and a edge between those nodes. The same transition of terms of the different users will result in several edges between the nodes.
I'd now like to create an edge with a cumulated weight of 4 to represent that 4 users did this specific transition. How can I combine the results of a count(*) query with a create query to produce an edge with a property weight = 4
My count(*) query is:
MATCH (n:Term)-[r]->(n1:Term)
RETURN type(r), count(*)
I'd expect the combined query to look like this, but this kind of sql like composition seems not to be possible in cypher:
MATCH (n:Term), (n1:Term)
WHERE (n)-[tr:TRANSITION]->(n1)
CREATE (n)-[actr:ACC_TRANSITION {count:
MATCH (n:Term)-[r]->(n1:Term) RETURN
count(*)}
]->(n1)
RETURN n, n1
A non generic query to produce the accumulated transition that works is:
MATCH (n:Term), (n1:Term)
WHERE n.term = 'iphone' AND n1.term ='iphone 6s'
CREATE (n)-[actr:ACC_TRANSITION {count: 4}]->(n1)
RETURN n, n1
Any other ideas on how to approach and model this problem?
Use WITH like this:
MATCH (n:Term)-[r]->(n1:Term)
WITH n as n, count(*) as rel_count, n1
CREATE (n)-[:ACC_TRANSITION {count:rel_count}]->(n1)
RETURN n, n1
If you match the nodes and relationship first and then use set, you will not produce duplicate nodes or relationships
Match (n:Term)-[r]->(n1.Term)
with n as nn,count(r) as rel_count,n1 as nn1
set r.ACC_TRANSITION=rel_count
return nn,nn1,r
The create function will create duplicates.

Traverse Graph With Directed Cycles using Relationship Properties as Filters

I have a Neo4j graph with directed cycles. I have had no issue finding all descendants of A assuming I don't care about loops using this Cypher query:
match (n:TEST{name:"A"})-[r:MOVEMENT*]->(m:TEST)
return n,m,last(r).movement_time
The relationships between my nodes have a timestamp property on them, movement_time. I've simulated that in my test data below using numbers that I've imported as floats. I would like to traverse the graph using the timestamp as a constraint. Only follow relationships that have a greater movement_time than the movement_time of the relationship that brought us to this node.
Here is the CSV sample data:
from,to,movement_time
A,B,0
B,C,1
B,D,1
B,E,1
B,X,2
E,A,3
Z,B,5
C,X,6
X,A,7
D,A,7
Here is what the graph looks like:
I would like to calculate the descendants of every node in the graph and include the timestamp from the last relationship using Cypher; so I'd like my output data to look something like this:
Node:[{Descendant,Movement Time},...]
A:[{B,0},{C,1},{D,1},{E,1},{X,2}]
B:[{C,1},{D,1},{E,1},{X,2},{A,7}]
C:[{X,6},{A,7}]
D:[{A,7}]
E:[{A,3}]
X:[{A,7}]
Z:[{B,5}]
This non-Neo4J implementation looks similar to what I'm trying to do: Cycle enumeration of a directed graph with multi edges
This one is not 100% what you want, but very close:
MATCH (n:TEST)-[r:MOVEMENT*]->(m:TEST)
WITH n, m, r, [x IN range(0,length(r)-2) |
(r[x+1]).movement_time - (r[x]).movement_time] AS deltas
WHERE ALL (x IN deltas WHERE x>0)
RETURN n, collect(m), collect(last(r).movement_time)
ORDER BY n.name
We basically find all the paths between any of your nodes (beware cartesian products get very expensive on non-trivial datasets). In the WITH we're building a collection delta's that holds the difference between two subsequent movement_time properties.
The WHERE applies an ALL predicate to filter out those having any non-positive value - aka we guarantee increasing values of movement_time along the path.
The RETURN then just assembles the results - but not as a map, instead one collection for the reachable nodes and the last value of movement_time.
The current issue is that we have duplicates since e.g. there are multiple paths from B to A.
As a general notice: this problem is much more elegantly and more performant solvable by using Java traversal API (http://neo4j.com/docs/stable/tutorial-traversal.html). Here you would have a PathExpander that skips paths with decreasing movement_time early instead of collection all and filter out (as Cypher does).

Cypher - neo4j find recursive end nodes

I'm trying to find all beginning nodes for a given node type, starting with any node in my database. These are nodes that have no relationships pointing to them.
I'm currently doing it manually, but need a recursive-type of statement to simplify and expand. Here is what I have so far, with a return statement describing what I need.
MATCH ((d1:Type1 {Name: "test1"}))
MATCH ((t1:Type2)-[:Rel1]->(h1:Type3))
MATCH ((d1)<-[ud1:Rel3]-(t1))
OPTIONAL MATCH ((h1)<-[:Rel1]-(t2:Type2))
OPTIONAL MATCH ((t2)<-[:Rel2]-(d2:Type1))
OPTIONAL MATCH ((d2)<-[ud2:Rel3]-(t3:Type2))
OPTIONAL MATCH ((t3)-[:Rel1]->(h2:Type3))
OPTIONAL MATCH ((h2)<-[:Rel1]-(t4:Type2))
OPTIONAL MATCH ((t4)<-[:Rel2]-(d3:Type1))
OPTIONAL MATCH ((d3)<-[ud3:Rel3]-(t5:Type2))
RETURN DISTINCT Type1.Name where there is no Rel3 relationship
The ask here is to navigate the recursive
Type1 < - Type2 -> Type3 <- Type2 < - Type1 < - Type2 -> Type3 <- Type2
path, until there are no Type2s pointing to the Type1, and return the names of these Type1s.
Your query example is quite complex and contains stand-in types that make it a bit hard to understand.
I think this boils down to something quite simple, you should try this:
MATCH (startingPoint:Type1 { name: "test1" })
MATCH (startingPoint)-[:relType*]->(headNode)
OPTIONAL MATCH (headNode)-[f:relType]->()
WHERE f is null;
So all this does is match from a startingPoint through any number of relationships to a headNode. How do we know that the headNode is really at the beginning? Because we insist with the OPTIONAL MATCH that it cannot be connected to anything else further upstream. We attempt to match what would be another upstream match, and then insist with the WHERE clause that it must be missing, hence headNode is really "at the top".
Customize this with your own relTypes and labels, and you should be able to follow this pattern.
More broadly, when you're asking about a beginning node and it not having some relationships, See also this related question.
FrobberOfBits has a great answer, but doesn't address the need to navigate through mutiple nodes types recursively. I've still yet to see a syntax that allows for this type of recursive querying in Cypher.
The work around is to simply create a new relationship using the complex traversal, then use FrobberOfBits solution to find the headnode.
Here is what I came up with:
MATCH ((d1:Type1))
MATCH ((q1:Type2)-[:Rel1]->(p1:Type3))
MATCH ((d1)<-[ud1:Rel2]-(q1))
MATCH ((p1)<-[:Rel1]-(q2:Type2))
MATCH ((q2)<-[:Rel3]-(d2:Type1))
WITH distinct d1 as child,d2 as parent
CREATE (child)<-[:ParentOf]-(parent)
This creates a new relationship type between two Type1s, removing the need to navigate through the different node types recursively, and simply allowing tree-traversal on a single node type (As FrobberOfBits masterfully explained)
Here is my resulting query using the types in the question and the above create statement, with an alternate syntax to Fibber's.
MATCH((d1:Type1 {Name: "test1"}))
OPTIONAL MATCH ((d1)<-[r1:Rel1*]-(d2:Type1))
WHERE NOT (d2)<--()
return distinct d2.Name
Sorry for the poor type masking! I should have been more creative with my aliasing.

Find path for N levels with repeating pattern of directional relationships in Neo4J

I'm trying to use Neo4j to analyze relationships in a family tree. I've modeled it like so:
(p1:Person)-[:CHILD]->(f:Family)<-[:FATHER|MOTHER]-(p2)
I know I could have left out the family label and just had children connected to each parent, but that's not practical for my purposes. Here's an example of my graph and the black line is the path I want it to generate:
I can query for it with
MATCH p=(n {personID:3})-[:CHILD]->()<-[:FATHER|MOTHER]-()-[:CHILD]->()<-[:FATHER|MOTHER]-()-[:CHILD]->()<-[:FATHER|MOTHER]-() RETURN p
but there's a repeating pattern to the relationships. Could I do something like:
MATCH p=(n {personID:3})(-[:CHILD]->()<-[:FATHER|MOTHER]-())* RETURN p
where the * means repeat the :CHILD then :FATHER|MOTHER relationships, with the directions being different? Obviously if the relationships were all the same direction, I could use
-[:CHILD|FATHER|MOTHER*]->
I want to be able to query it from Person #3 all the way to the top of the graph like a pedigree chart, but also be specific about how many levels if needed (like 3 generations, as opposed to end-of-line).
Another issue I'm having with this, is if I don't put directions on the relationships like -[:CHILD|FATHER|MOTHER*]-, then it will start at Person #3, and go both in the direction I want (alternating arrows), but also descend back down the chain finding all the other "cousins, aunts, uncles, etc.".
Any seasoned Cypher experts that an help me?
I am just on the same problem. And I found out that the APOC Expand path procedures are just accomplishing what you/we want.
Applied to your example, you could use apoc.path.subgraphNodes to get all ancestors of Person #3:
MATCH (p1:Person {personId:3})
CALL apoc.path.subgraphNodes(p1, {
sequence: '>Person,CHILD>,Family,<MOTHER|<FATHER'
}) YIELD node
RETURN node
Or if you want only ancestors up to the 3 generations from start person, add maxLevel: 6 to config (as one generation is defined by 2 relationships, 3 generations are 6 levels):
MATCH (p1:Person {personId:3})
CALL apoc.path.subgraphNodes(p1, {
sequence: '>Person,CHILD>,Family,<MOTHER|<FATHER',
maxLevel: 6
}) YIELD node
RETURN node
And if you want only ancestors of 3rd generation, i.e. only great-grandparents, you can also specify minLevel (using apoc.path.expandConfig):
MATCH (p1:Person {personId:3})
CALL apoc.path.expandConfig(p1, {
sequence: '>Person,CHILD>,Family,<MOTHER|<FATHER',
minLevel: 6,
maxLevel: 6
}) YIELD path
WITH last(nodes(path)) AS person
RETURN person
You could reverse the directionality of the CHILD relationships in your model, as in:
(p1:Person)<-[:CHILD]-(f:Family)<-[:FATHER|MOTHER]-(p2)
This way, you can use a simple -[:CHILD|FATHER|MOTHER*]-> pattern in your queries.
Reversing the directionality is actually intuitive as well, since you can then more naturally visualize the graph as a family tree, with all the arrows flowing "downwards" from ancestors to descendants.
Yeah, that's an interesting case. I'm pretty sure (though I'm open to correction) that this is just not possible. Would it be possible for you to have and maintain both? You could have a simple cypher query create the extra relationships:
MATCH (parent)-[:MOTHER|FATHER]->()<-[:CHILD]-(child)
CREATE (child)-[:CHILD_OF]->parent
Ok, so here's a thought:
MATCH path=(child:Person {personID: 3})-[:CHILD|FATHER|MOTHER*]-(ancestor:Person),
WHERE ancestor-[:MOTHER|FATHER]->()
RETURN path
Normally I'd use a second clause in the MATCH like this:
MATCH
path=(child:Person {personID: 3})-[:CHILD|FATHER|MOTHER*]-(ancestor:Person),
ancestor-[:MOTHER|FATHER]->()
RETURN path
But Neo4j (at least by default, I think) doesn't traverse back through the path. Maybe comma-separating would be fine and this would be a problem:
MATCH path=(child:Person {personID: 3})-[:CHILD|FATHER|MOTHER]-(ancestor:Person)-[:MOTHER|FATHER]->()
I'm curious to know what you find!

Path properties in Cypher

I have a following graph in Neo4j
(id:5,t:e)<--(id:4,t:w)<--(id:0;t:s)-->(id:1,t:w)-->(id:2,t:b)-->(id:3,t:e)
now I search paths from nodes with t:s to nodes with t:e such that only white-listed nodes with t:w are in-between.
So ideally i need a query to return only (0)-->(4)-->(5) but not (0)-->(1)-->(2)-->(3).
EDIT: i have forgotten to mention that paths may have variable length: from 0 to potentially infinity. It means that I may have an arbitrary number of "t:w" nodes
Best regards
Working just with the information that you have provided above you could use
MATCH p=({t:'s'})-->({t:'w'})-->({t:'e'}) RETURN p
Of course if an s could link directly to an e you will need to use variable length relationships matches.
MATCH p=({t:'s'})-[*0..1]->({t:'w'})-[]->({t:'e'})
RETURN DISTINCT p
EDIT - Paths of any length
MATCH p=({t:'s'})-[*0..1]->({t:'w'})-[*]->({t:'e'})
RETURN DISTINCT p
To match a path of any length use the * operator in the relationship path match. It is usually best to put some bounds on that match, an example of which is the *0..1 (length 0 to 1). You can leave either end open *..6 (length 1 to 6) or *2.. (length 2 to whatever).
The problem with this is that now you cannot guarantee the node types in the intervening nodes (so t:"b" will be matched). To avoid that I think you'll have to filter.
MATCH p=({t:'s'})-[*]->({t:'e'})
WHERE ALL (node IN NODES(p)
WHERE node.t = 's' OR node.t = 'w' OR node.t = 'e' )
RETURN p
End Edit
You should introduce labels to your nodes and use relationship types for traversal though as that is where Neo/Cypher is going to be able to help you out. You should also make sure that if you are matching on properties that they are indexed correctly.

Resources