3
votes

I want to Traverse a PATH in neo4j (preferably using Cypher, but I can write neo4j managed extensions).

Problem - For any starting node (:Person) I want to traverse hierarchy like

(me:Person)-[:FRIEND|:KNOWS*]->(newPerson:Person)

if the :FRIEND outgoing relationship is present then the path should traverse that, and ignore any :KNOWS outgoing relationships, if :FRIEND relationship does not exist but :KNOWS relationship is present then the PATH should traverse that node.

Right now the problem with above syntax is that it returns both the paths with :FRIEND and :KNOWS - I am not able to filter out a specific direction based on above requirement.

3
Can you confirm that you always want to use a FRIEND over a KNOWS, regardless of the total "cost" of the path. For example - which would be your preferred path from P1 to P4 assuming both are valid: (P1) -[:KNOWS]-> (P2)-[:FRIEND]-> (P3)-[:FRIEND]-> (P4) or (P1) -[:FRIEND]-> (P5)-[:KNOWS]-> (P6)-[:KNOWS]-> (P4)dbarton_uk
Yes, I want to choose :FRIEND over :KNOWS ( one over other) at every step, no matter if it is a longer path.msameep
For this kind of case, favoring one relationship type over another at every step in the expansion, you really need to write your own custom procedure using the traversal API. Otherwise you'll need to resort to expanding everything using both relationships and then filtering after the fact to only keep the paths that adhere to your requirement.InverseFalcon
Thank you InverseFalcon. I will try write custom API. can you please provide some insights on how should I use Evaluators for this requirements.msameep
In your PathExpander you return the relationships to follow, based on the type of the relationships on the previous or current node you can just return node.getRelationships(type) there.Michael Hunger

3 Answers

1
votes

1. Example data set

For the ease of possible further answers and solutions I note my graph creating statement:

CREATE
  (personA:Person {name:'Person A'})-[:FRIEND]->(personB:Person {name: 'Person B'}),
  (personB)-[:FRIEND]->(personC:Person {name: 'Person C'}),
  (personC)-[:FRIEND]->(personD:Person {name: 'Person D'}),
  (personC)-[:FRIEND]->(personE:Person {name: 'Person E'}),
  (personE)-[:FRIEND]->(personF:Person {name: 'Person F'}),
  (personA)-[:KNOWS]->(personG:Person {name: 'Person G'}),
  (personA)-[:KNOWS]->(personH:Person {name: 'Person H'}),
  (personH)-[:KNOWS]->(personI:Person {name: 'Person I'}),
  (personI)-[:FRIEND]->(personJ:Person {name: 'Person J'});

graph 1

2. Scenario "Optional Match"

2.1 Solution

MATCH (startNode:Person {name:'Person A'})
OPTIONAL MATCH friendPath = (startNode)-[:FRIEND*]->(:Person)
OPTIONAL MATCH knowsPath = (startNode)-[:KNOWS*]->(:Person)
RETURN friendPath, knowsPath;

If you do not need every path to all nodes of the entire path, but only the whole, I recommend using shortestPath() for performance reasons.

2.1 Result

Note the missing node 'Person J', because it owns a FRIENDS relationship to node 'Person I'.

graph 2

3. Scenario "Expand paths"

3.1 Solution

Alternatively you could use the Expand paths functions of the APOC user library. Depending on the next steps of your process you can choose between the identification of nodes, relationships or both.

MATCH (startNode:Person {name:'Person A'})
CALL apoc.path.subgraphNodes(startNode,
  {maxLevel: -1, relationshipFilter: 'FRIEND>', labelFilter: '+Person'}) YIELD node AS friendNodes
CALL apoc.path.subgraphNodes(startNode,
  {maxLevel: -1, relationshipFilter: 'KNOWS>', labelFilter: '+Person'}) YIELD node AS knowsNodes
WITH
  collect(DISTINCT friendNodes.name) AS friendNodes,
  collect(DISTINCT knowsNodes.name) AS knowsNodes
RETURN friendNodes, knowsNodes;

3.2 Explanation

  • line 1: defining your start node based on the name
  • line 2-3: Expand from the given startNode following the given relationships (relationshipFilter: 'FRIEND>') adhering to the label filter (labelFilter: '+Person').
  • line 4-5: Expand from the given startNode following the given relationships (relationshipFilter: 'KNOWS>') adhering to the label filter (labelFilter: '+Person').
  • line 7: aggregates all nodes by following the FRIEND relationship type (omit the .name part if you need the complete node)
  • line 8: aggregates all nodes by following the KNOWS relationship type (omit the .name part if you need the complete node)
  • line 9: render the resulting groups of nodes

3.3 Result

╒═════════════════════════════════════════════╤═════════════════════════════════════════════╕
│"friendNodes""knowsNodes"                                 │
╞═════════════════════════════════════════════╪═════════════════════════════════════════════╡
│["Person A","Person B","Person C","Person E",│["Person A","Person H","Person G","Person I"]│
│"Person D","Person F"]                       │                                             │
└─────────────────────────────────────────────┴─────────────────────────────────────────────┘
0
votes
MATCH p = (me:Person)-[:FRIEND|:KNOWS*]->(newPerson:Person) 
WITH p, extract(r in relationships(p) | type(r)) AS types
RETURN p ORDER BY types asc LIMIT 1
0
votes

This is a matter of interrogating the types of outgoing relationships for each node and then making a prioritized decision on which relationships to retain leveraging some nested case logic.

Using the small graph above

MATCH path = (a)-[r:KNOWS|FRIEND]->(b) WITH a, COLLECT([type(r),a,r,b]) AS rels WITH a, rels, CASE WHEN filter(el in rels WHERE el[0] = "FRIEND") THEN filter(el in rels WHERE el[0] = "FRIEND") ELSE CASE WHEN filter(el in rels WHERE el[0] = "KNOWS") THEN filter(el in rels WHERE el[0] = "KNOWS") ELSE [''] END END AS search UNWIND search AS s RETURN s[1] AS a, s[2] AS r, s[3] AS b

I believe this returns your expected result:

enter image description here

Based on your logic, there should be no traversal to Person G or Person H from Person A, as there is a FRIEND relationship from Person A to Person B that takes precedence.

However there is a traversal from Person H to Person I because of the existence of the singular KNOWS relationship, and then a subsequent traversal from Person I to Person J.