1
votes

What I'm looking for

With variable length relationships (see here in the neo4j manual), it is possible to have a variable number of relationships with a certain label between two nodes.

# Cypher
match (g1:Group)-[:sub_group*]->(g2:Group) return g1, g2

I'm looking for the same thing with nodes, i.e. a way to query for two nodes with a variable number of nodes in between, but with a label condition on the nodes rather than the relationships:

# Looking for something like this in Cypher:
match (g1:Group)-->(:Group*)-->(g2:Group) return g1, g2

Example

I would use this mechanism, for example, to find all (direct or indirect) members of a group within a group structure.

# Looking for somthing like this in Cypher:
match (group:Group)-->(:Group*)-->(member:User) return member

Take, for example, this structure:

group1:Group
   |-------> group2:Group -------> user1:User
   |-------> group3:Group
                  |--------> page1:Page -----> group4:Group -----> user2:User

In this example, user1 is a member of group1 and group2, but user2 is only member of group4, not member of the other groups, because a non-Group labeled node is in between.

Abstraction

A more abstract pattern would be a kind of repeat operator |...|* in Cypher:

# Looking for repeat operator in Cypher:
match (g1:Group)|-[:is_subgroup_of]->(:Group)|*-[:is_member_of]->(member:User) 
return member

Does anyone know of such a repeat operator? Thanks!

2

2 Answers

2
votes

Possible Solution

One solution I've found, is to use a condition on the nodes using where, but I hope, there is a better (and shorter) soluation out there!

# Cypher
match path = (member:User)<-[*]-(g:Group{id:1}) 
where all(node in tail(nodes(path)) where ('Group' in labels(node))) 
return member

Explanation

In the above query, all(node in tail(nodes(path)) where ('Group' in labels(node))) is one single where condition, which consists of the following key parts:

  • all: ALL(x in coll where pred): TRUE if pred is TRUE for all values in coll
  • nodes(path): NODES(path): Returns the nodes in path
  • tail(): TAIL(coll): coll except first element–––I'm using this, because the first node is a User, not a Group.

Reference

1
votes

How about this:

MATCH (:Group {id:1})<-[:IS_SUBGROUP_OF|:IS_MEMBER_OF*]-(u:User)
RETURN DISTINCT u

This will:

  • find all subtrees of the group with ID 1
  • only traverse the relationships IS_GROUP_OF and IS_MEMBER_OF in incoming direction (meaning sub-groups or users that belong to group with ID or one of its sub-groups)
  • only return nodes which have a IS_MEMBER_OF relationship to a group in the subtree
  • and discard duplicate results (users who belong to more than one of the groups in the tree would otherwise appear multiple times)

I know this relies on relationships types rather than node labels, but IMHO this is a more graphy approach.

Let me know if this would work or not.