Neo4 hierachy traversal

I have some hierarchical data stored in neo4j and need a query to find all the children of the parent node for a specific user. Main scenario:

(Root Task {control: 1})
    (Child Task 2 {control: 2})
    (Child Task 3 {control: 3})
        (Child Task 4 {control: 4})

      

Child tasks have a CHILD_OF relationship to their parent. So it's okay and I can get the kids from the parent. Using the following query, I am returning Child Task 4.

MATCH (rootTask:Task {control: 3}), (user:User {control: 60})
,(childTask:Task)-[:CHILD_OF*]->(rootTask)
WHERE (user)-[:LINKED_TO]->(childTask)
RETURN childTask

      

The problem is the need to reset the user structure, but only for yourself. So I introduced a new relationship that contains a link to the user. CHILD_OF_ is added, and if it exists, it should take precedence over CHILD_OF.

So if user 60 solves a problem (problem for child problem 4) falls under (root problem) and not (child problem 3), two links are created:

MERGE (Level 4 task)-[:CHILD_OF]->(Child Task 3)
MERGE (Level 4 task)-[:CHILD_OF_60]->(Root Task)

      

His view is now mostly:

(Root Task {control: 1})
    (Child Task 2 {control: 2})
    (Child Task 3 {control: 3})
    (Child Task 4 {control: 4})

      

So now when I ask for children (Child Task 3), for user 60 I don't want (Child Task 4).

Using the previous query and adding user dependent relationships and an additional constraint to not return a child task if it has a specific reference to an associated unrelated task almost works, expect it to return child objects (Child Task 4) because they are related only CHILD_OF relationships. The logic for excluding a childTask that has a link for a specific user is also flawed as it might actually point to the same parent.

MATCH (rootTask:Task {control: 3}), (user:User {control: 60})
,(childTask:Task)-[:CHILD_OF|CHILD_OF_60*]->(rootTask)
WHERE (user)-[:LINKED_TO]->(childTask) 
AND NOT (childTask)-[:CHILD_OF_60]-(:Task)
RETURN childTask

      

The gist of the logic I need is simply if a CHILD_OF_60 relationship exists for the task, following that relationship and ignoring the default CHILD_OF relationship.

Your help will be much appreciated. I haven't found a similar scenario after quite a lot of searching.


Test data

MERGE (ruan :User {control: 50, fullname: 'Ruan'})
MERGE (amber :User {control: 60, fullname: 'Amber'})
MERGE (task1 :Task {control: 1, subject: 'Root Task:'})
MERGE (task2 :Task {control: 2, subject: 'Child of Root:'})
MERGE (task3 :Task {control: 3, subject: 'User properties'})
MERGE (task4 :Task {control: 4, subject: 'User parent links'})
MERGE (task5 :Task {control: 5, subject: 'Hierarchy Traversal'})
MERGE (task6 :Task {control: 6, subject: 'Parent'})
MERGE (task7 :Task {control: 7, subject: 'Child'})
MERGE (task8 :Task {control: 8, subject: 'Query1'})
MERGE (task2)-[:CHILD_OF]->(task1)
MERGE (task3)-[:CHILD_OF]->(task2)
MERGE (task4)-[:CHILD_OF]->(task2)
MERGE (task5)-[:CHILD_OF]->(task2)
MERGE (task6)-[:CHILD_OF]->(task5)
MERGE (task7)-[:CHILD_OF]->(task5)
MERGE (task8)-[:CHILD_OF]->(task7)
MERGE (ruan)-[:LINKED_TO]->(task1)
MERGE (ruan)-[:LINKED_TO]->(task2)
MERGE (ruan)-[:LINKED_TO]->(task3)
MERGE (ruan)-[:LINKED_TO]->(task4)
MERGE (ruan)-[:LINKED_TO]->(task5)
MERGE (ruan)-[:LINKED_TO]->(task6)
MERGE (ruan)-[:LINKED_TO]->(task7)
MERGE (ruan)-[:LINKED_TO]->(task8)
MERGE (amber)-[:LINKED_TO]->(task1)
MERGE (amber)-[:LINKED_TO]->(task2)
MERGE (amber)-[:LINKED_TO]->(task3)
MERGE (amber)-[:LINKED_TO]->(task4)
MERGE (amber)-[:LINKED_TO]->(task5)
MERGE (amber)-[:LINKED_TO]->(task6)
MERGE (amber)-[:LINKED_TO]->(task7)
MERGE (amber)-[:LINKED_TO]->(task8)
MERGE (task2)-[:CHILD_OF]->(task1)
MERGE (task3)-[:CHILD_OF]->(task2)
MERGE (task4)-[:CHILD_OF]->(task2)
MERGE (task5)-[:CHILD_OF]->(task2)
MERGE (task6)-[:CHILD_OF]->(task5)
MERGE (task7)-[:CHILD_OF]->(task5)
MERGE (task8)-[:CHILD_OF]->(task7)
MERGE (task5)-[:CHILD_OF_60]->(task1)
MERGE (task3)-[:CHILD_OF_60]->(task1)

      

+3


source to share


1 answer


It was fun to work with. I've compiled a GraphGist here to demonstrate my suggestion:

http://graphgist.neo4j.com/#!/gists/54d8b5ef8cfb85197aa4

But I will also put a solution here:

MATCH
  (rootTask:Task { control: 1 }),
  path=(childTask:Task)-[:CHILD_OF|CHILD_OF_60*1..]->rootTask,
  (user:User { control: 60 })-[:LINKED_TO]->childTask
WITH childTask, path AS the_path, path
UNWIND nodes(path) AS node
OPTIONAL MATCH node-[:CHILD_OF_60]->(otherParent:Task)
WITH childTask, the_path, collect(otherParent IS NULL OR otherParent IN nodes(the_path))[0..-1] AS otherParentResults
WHERE ALL(result IN otherParentResults WHERE result)
RETURN DISTINCT childTask

      



Basically I get the path by checking if the leaf node has a different parent via a relationship CHILD_OF_60

and then return the child if it doesn't have another parent or if the other parameter is not in the ancestor path.

I would feel more comfortable with this solution if it was backed up by automated tests, but give it a try!;)

Also, as a rule of thumb, I try not to create relationship variable names. You may want to have an optional property user_id

in your relationship CHILD_OF

. Alternatively, you can have something like a relationship type CHILD_OF_FOR_USER

that has a property user_id

.

EDIT: I edited the request above and GraphGist to take care of the child nodes with moved ancestors in the path to the root node

+5


source







All Articles