6.2. Hyperedges

Imagine a user being part of different groups. A group can have different roles, and a user can be part of different groups. He also can have different roles in different groups apart from the membership. The association of a User, a Group and a Role can be referred to as a HyperEdge. However, it can be easily modeled in a property graph as a node that captures this n-ary relationship, as depicted below in the U1G2R1 node.

Figure 6.1. Graph

Find Groups

To find out in what roles a user is for a particular groups (here Group2), the following query can traverse this HyperEdge node and provide answers.

Query 

MATCH ({ name: 'User1' })-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->({ name: 'Group2' }),
  (hyperEdge)-[:hasRole]->(role)
RETURN role.name

The role of User1 is returned:

Result

role.name
1 row

"Role1"

Try this query live create (_0 {`name`:"Group1"}) create (_1 {`name`:"Role"}) create (_2 {`name`:"Group2"}) create (_3 {`name`:"Group"}) create (_4 {`name`:"User1"}) create (_5 {`name`:"U1G2R1"}) create (_6 {`name`:"U1G1R2"}) create (_7 {`name`:"Role2"}) create (_8 {`name`:"Role1"}) create (_0)-[:`isA`]->(_3) create (_0)-[:`canHave`]->(_7) create (_0)-[:`canHave`]->(_8) create (_2)-[:`isA`]->(_3) create (_2)-[:`canHave`]->(_8) create (_2)-[:`canHave`]->(_7) create (_4)-[:`hasRoleInGroup`]->(_6) create (_4)-[:`hasRoleInGroup`]->(_5) create (_4)-[:`in`]->(_2) create (_4)-[:`in`]->(_0) create (_5)-[:`hasGroup`]->(_2) create (_5)-[:`hasRole`]->(_8) create (_6)-[:`hasGroup`]->(_0) create (_6)-[:`hasRole`]->(_7) create (_7)-[:`isA`]->(_1) create (_8)-[:`isA`]->(_1) ; match ({name: 'User1'})-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->({name: 'Group2'}), (hyperEdge)-[:hasRole]->(role) return role.name

Find all groups and roles for a user

Here, find all groups and the roles a user has, sorted by the name of the role.

Query 

MATCH ({ name: 'User1' })-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->(group),
  (hyperEdge)-[:hasRole]->(role)
RETURN role.name, group.name
ORDER BY role.name ASC

The groups and roles of User1 are returned:

Result

role.namegroup.name
2 rows

"Role1"

"Group2"

"Role2"

"Group1"

Try this query live create (_0 {`name`:"Group1"}) create (_1 {`name`:"Role"}) create (_2 {`name`:"Group2"}) create (_3 {`name`:"Group"}) create (_4 {`name`:"User1"}) create (_5 {`name`:"U1G2R1"}) create (_6 {`name`:"U1G1R2"}) create (_7 {`name`:"Role2"}) create (_8 {`name`:"Role1"}) create (_0)-[:`isA`]->(_3) create (_0)-[:`canHave`]->(_7) create (_0)-[:`canHave`]->(_8) create (_2)-[:`isA`]->(_3) create (_2)-[:`canHave`]->(_8) create (_2)-[:`canHave`]->(_7) create (_4)-[:`hasRoleInGroup`]->(_6) create (_4)-[:`hasRoleInGroup`]->(_5) create (_4)-[:`in`]->(_2) create (_4)-[:`in`]->(_0) create (_5)-[:`hasGroup`]->(_2) create (_5)-[:`hasRole`]->(_8) create (_6)-[:`hasGroup`]->(_0) create (_6)-[:`hasRole`]->(_7) create (_7)-[:`isA`]->(_1) create (_8)-[:`isA`]->(_1) ; match ({name: 'User1'})-[:hasRoleInGroup]->(hyperEdge)-[:hasGroup]->(group), (hyperEdge)-[:hasRole]->(role) return role.name, group.name order by role.name asc

Find common groups based on shared roles

Assume a more complicated graph:

  1. Two user nodes User1, User2.
  2. User1 is in Group1, Group2, Group3.
  3. User1 has Role1, Role2 in Group1; Role2, Role3 in Group2; Role3, Role4 in Group3 (hyper edges).
  4. User2 is in Group1, Group2, Group3.
  5. User2 has Role2, Role5 in Group1; Role3, Role4 in Group2; Role5, Role6 in Group3 (hyper edges).

The graph for this looks like the following (nodes like U1G2R23 representing the HyperEdges):

Figure 6.2. Graph

To return Group1 and Group2 as User1 and User2 share at least one common role in these two groups, the query looks like this:

Query 

MATCH (u1)-[:hasRoleInGroup]->(hyperEdge1)-[:hasGroup]->(group),(hyperEdge1)-[:hasRole]->(role),
  (u2)-[:hasRoleInGroup]->(hyperEdge2)-[:hasGroup]->(group),(hyperEdge2)-[:hasRole]->(role)
WHERE u1.name = 'User1' AND u2.name = 'User2'
RETURN group.name, count(role)
ORDER BY group.name ASC

The groups where User1 and User2 share at least one common role:

Result

group.namecount(role)
2 rows

"Group1"

1

"Group2"

1

Try this query live create (_0 {`name`:"U1G1R12"}) create (_1 {`name`:"U1G2R23"}) create (_2 {`name`:"User2"}) create (_3 {`name`:"User1"}) create (_4 {`name`:"Role5"}) create (_5 {`name`:"Role4"}) create (_6 {`name`:"Role3"}) create (_7 {`name`:"U2G1R25"}) create (_8 {`name`:"Role2"}) create (_9 {`name`:"Role1"}) create (_10 {`name`:"U2G3R56"}) create (_11 {`name`:"U2G2R34"}) create (_12 {`name`:"Group1"}) create (_13 {`name`:"Group2"}) create (_14 {`name`:"Group3"}) create (_15 {`name`:"Role6"}) create (_16 {`name`:"U1G3R34"}) create (_0)-[:`hasRole`]->(_8) create (_0)-[:`hasRole`]->(_9) create (_0)-[:`hasGroup`]->(_12) create (_1)-[:`hasRole`]->(_6) create (_1)-[:`hasRole`]->(_8) create (_1)-[:`hasGroup`]->(_13) create (_2)-[:`hasRoleInGroup`]->(_10) create (_2)-[:`hasRoleInGroup`]->(_11) create (_2)-[:`hasRoleInGroup`]->(_7) create (_3)-[:`hasRoleInGroup`]->(_16) create (_3)-[:`hasRoleInGroup`]->(_1) create (_3)-[:`hasRoleInGroup`]->(_0) create (_7)-[:`hasRole`]->(_4) create (_7)-[:`hasRole`]->(_8) create (_7)-[:`hasGroup`]->(_12) create (_10)-[:`hasRole`]->(_4) create (_10)-[:`hasRole`]->(_15) create (_10)-[:`hasGroup`]->(_14) create (_11)-[:`hasRole`]->(_6) create (_11)-[:`hasRole`]->(_5) create (_11)-[:`hasGroup`]->(_13) create (_16)-[:`hasRole`]->(_5) create (_16)-[:`hasRole`]->(_6) create (_16)-[:`hasGroup`]->(_14) ; match (u1)-[:hasRoleInGroup]->(hyperEdge1)-[:hasGroup]->(group), (hyperEdge1)-[:hasRole]->(role), (u2)-[:hasRoleInGroup]->(hyperEdge2)-[:hasGroup]->(group), (hyperEdge2)-[:hasRole]->(role) where u1.name = 'User1' and u2.name = 'User2' return group.name, count(role) order by group.name ASC