cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

RETURNing correct graph (of hierarchies) but confused on RETURNing tabular data

Hi all (newbie here)

I've got a graph that contains mostly hierarchical information: every node is a "Thing" and all relationships are "IS_IN".

e.g. (chair5)-[:IS_IN]->(room2)-[:IS_IN]->(apartment17)-[:IS_IN]->(building1)-[:IS_IN]->(neighborhood1)

I'd like to return all Things that are both IN the queried Thing, and all Things in which the queried thing IS IN. (e.g. If I query for 'apartment17' then I'd see all chairs and rooms in apt17, and also the building and neighborhood that apt17 is in.)

I can see in the graph that this query works:

MATCH (sm:Thing)-[:IS_IN*]->(t:Thing {name: 'apt 17'})-[:IS_IN*]->(lg:Thing)
RETURN *

However I'd like to return the data as a table, but when I do the above MATCH with

RETURN sm.name, t.name, lg.name

the returned data doesn't show the hierarchy.

Any thoughts?

TIA,
-Joel

1 ACCEPTED SOLUTION

I think the following works. It finds the entire path that goes through a specified node. The list of nodes returned are ordered from left to right in the pattern, so it maintains the hierarchy, i.e. nodes[n] 'is in' nodes[n+1], for all valid n.

Note, x can be any node on the path, since the variable length matches include zero length.

match p = (begin:Thing)-[:IS_IN*0..]->(x:Thing)-[:IS_IN*0..]->(end: Thing)
where x.name= 'apt 17'
and not exists ((end)-[:IS_IN]->(:Thing))
and not exists (()-[:IS_IN]->(begin:Thing))
return nodes(p) as nodes

View solution in original post

4 REPLIES 4

I think the following works. It finds the entire path that goes through a specified node. The list of nodes returned are ordered from left to right in the pattern, so it maintains the hierarchy, i.e. nodes[n] 'is in' nodes[n+1], for all valid n.

Note, x can be any node on the path, since the variable length matches include zero length.

match p = (begin:Thing)-[:IS_IN*0..]->(x:Thing)-[:IS_IN*0..]->(end: Thing)
where x.name= 'apt 17'
and not exists ((end)-[:IS_IN]->(:Thing))
and not exists (()-[:IS_IN]->(begin:Thing))
return nodes(p) as nodes

Thanks Gary!

That works great!

Can I ask 2 questions:

A. Can you explain in English what these 2 lines mean?

and not exists ((end)-[:IS_IN]->(:Thing))
and not exists (()-[:IS_IN]->(begin:Thing))

B. Why do you use [:IS_IN*0..] instead of just [:IS_IN*]?

You are very welcome.

In regards to question 'A', the variable length pattern will result in many paths that match. You can see this using the same data as above and removing the conditions. The results below are when I specify the 'x' node as the 'apartment.' The result is six paths that match the pattern. You see that many are paths that are a partial path of the whole path,. In order to get the entire path, we can add constrains to the pattern to filter out the partial paths. One constraint would be that the node bound to the 'begin' variable be a starting node of the path, i.e. that it does not have an incoming IS_IN relationship. Only the 'chair' node meets this criteria, so adding this will eliminate all the partial paths in the results below that do not start with the 'chair' node.

The other criteria is for the node bound to the 'end' variable to be an end node, i.e. it does not have an outgoing relationship to another Thing node. Only the building node meets this condition. Adding this will filter out from the above results any path that does not end on the 'building' node. The only path matching path conditions is the last path in the result, which is the only one that represents the entire path.

The not exists ((end)-[:IS_IN]->(:Thing)) condition states that there can't exist an outgoing IS_IN relationship from the 'end' node to any 'Thing' node.

The not exists (()-[:IS_IN]->(begin:Thing)) condition states that there can't exist an incoming IS_IN relationship to the 'begin' node from any node.

I notice the two conditions are written a little differntly, so use this version instead:

match p = (begin:Thing)-[:IS_IN*0..]->(x:Thing)-[:IS_IN*0..]->(end: Thing)
where x.name= 'apt 17'
and not exists ((end)-[:IS_IN]->(:Thing))
and not exists ((:Thing)-[:IS_IN]->(begin))
return nodes(p) as nodes

In regards to question B, when you specify "*' it represents a min length of one and an unbounded max length. I used "*0.." to specify a min length of zero. This allows the node bound to the 'x' node to be the 'begin' node or the 'end' node if necessary. For instance, the two queries will work when using the zero min length condition:

match p = (begin:Thing)-[:IS_IN*0..]->(x: Thing)-[:IS_IN*0..]->(end: Thing)
where x.type = 'chair'
and not exists ((end)-[:IS_IN]->(: Thing))
and not exists ((: Thing)-[:IS_IN]->(begin))
return nodes(p) as nodes

or

match p = (begin:Thing)-[:IS_IN*0..]->(x: Thing)-[:IS_IN*0..]->(end: Thing)
where x.type = 'building'
and not exists ((end)-[:IS_IN]->(: Thing))
and not exists ((: Thing)-[:IS_IN]->(begin))
return nodes(p) as nodes

If you use a min length of one, then neither of the two queries would return a result, as the 'x' node has to be a minimum of one hope away from both the 'chair' and 'building' nodes.

Thanks so much, Gary! Very helpful