Query for "mutual friendship" (explicitly set in both directions)

I am in my initial exploration of dgraph and using the basic data from the demo (with some small alterations). The relevant alteration for this question is to the schema:

friendship: [uid] @count @reverse .

I want to build a query that gives incoming, outgoing, and mutual friendships (explicitly bidirectional - meaning that there is an edge set for both directions, not only via @reverse). So everything except the line for friendships_mutual works in the following query:

{
  everyone(func: eq(dgraph.type, "Person")) {
    uid
    name@* @facets
    friendships_outgoing: count(friendship)
    friendships_incoming: count(~friendship)
    friendships_mutual: ?? HOW_TO ??
    friendships: friendship @facets {
      uid
      name@* @facets
    }
  }
}

Is there a way to filter and count friendships that are explicitly bidirectional?

Try something like

friendships_mutual: math(incoming = outgoing)

It should return true or false

Thanks @MichelDiz for the quick reply…

  1. i guess you mean incoming == outgoing, right?

  2. What should incoming and outgoing refer to?

I would somehow need to compare uids of the nested friendship triples… but there i have no idea how to isolate them. Intuitively i tried:

is_mutual: math(friendship.uid == uid)

but that doesn’t work.

any more ideas?

You have to use variables

    outgoing as friendships_outgoing: count(friendship)
    incoming as friendships_incoming: count(~friendship)

update: I’ll come back to your question. There are some points to clarify.

ok, so i can use variables in the way you suggest above,
and create a value for:

are_all_friendships_mutual: math(incoming == outgoing)

This “works” (returns results comparing the counts), however it is really not the point, as that only really means that a person has the same number of incoming and outgoing friends. But does not at all indicate that they are explicitly mutual.

Based on this dataset How to construct a query collecting all friends? - #9 by MichelDiz

{  
  G as var(func: eq(name, "User 7"))
  
 result(func: uid(G)) {
  name
  allFriends :friend @normalize {
    name:name
    friend @filter(uid(G)) {
      mutual : count(uid)
    }
   }
 }

}

Result

{
  "data": {
    "result": [
      {
        "name": "User 7",
        "allFriends": [
          {
            "name": "User 5"
          },
          {
            "mutual": 1,
            "name": "User 6"
          },
          {
            "name": "User 1"
          }
        ]
      }
    ]
  }
}

thanks, so far! this works perfectly for one user at a time…

how can i get it integrated into an everyone query:

{  
 everyone(func: eq(dgraph.type, "Person")) {
  rootuid: uid
  name:name@.
  friendships: friendship  {
    name:name@.
    frienduid: uid
    friends_friends:friendship  {
      name:name@.
      uid
    }
   }
 }
}

yields:

"everyone": [
      {
        "rootuid": "0x275c",
        "name": "Michael",
        "friendships": [
          {
            "name": "Amit",
            "frienduid": "0x2761",
            "friends_friends": [
              {
                "uid": "0x275c",
                "name": "Michael"
              },
              {
                "uid": "0x275d",
                "name": "Artyom"
              },
              {
                "uid": "0x2763",
                "name": "Sang Hyun"
              }
            ]
          },

which indicates that Michael and Amit are mutual friends…
but i can’t manage to get an is_mutual field anywhere : (

i also can’t manage to filter the inner friends_friends array using the rootuid.

any ideas?

This query looks like “Who of the friends of my friends is a mutual friend with me”. This should work fine. But as the way I was doing.

Yep, some bulk queries won’t work well. That’s why I have created this ticket Feature: Add foreach() function and FOREACH func in DQL (loops in Bulk Upsert) - please take a read to get what is happening. Is a long story.

BTW, the reason I have used a multi-blocks approach here is due to the fact that some variables won’t work in the same block or with multi-levels see Promise a nested block (under construction - I'm still working in the use case)

So, you have to use multi-blocks and do this one by one for now.

PS. BTW you can use cascade on my last query. So you gonna have just the mutual friends

{  
  G as var(func: eq(name, "User 7"))
  
 result(func: uid(G)) {
  name
  allFriends :friend @normalize @cascade {
    name:name
    friend @filter(uid(G)) {
      mutual : count(uid)
    }
   }
 }

}

ok, but i am confused about variables (and propagation)…

why doesn’t this show ANY is_mutual info?

{  
 everyone(func: eq(dgraph.type, "Person")) {
  	rootuid as uid
 
  name:name@.
  friendships: friendship {
    name:name@.
    frienduid: uid
    friends_friends:friendship  {
      targetuid as uid
      name:name@.
      is_mutual: math(rootuid==targetuid)
    }
  }
 }
}

and why doesn’t this show ANY friends_friends:

{  
 everyone(func: eq(dgraph.type, "Person")) {
  	rootuid as uid
 
  name:name@.
  friendships: friendship {
    name:name@.
    frienduid: uid
    friends_friends:friendship @filter(uid(rootuid)) {
      targetuid as uid
      name:name@.
      is_mutual: math(rootuid==targetuid)
    }
  }
 }
}

is it not possible to use uid as a variable?

As I said, this won’t work because of Promise a nested block (under construction - I'm still working in the use case). The variable won’t propagate in the same block. Only if it is an aggregation.

I’m not sure what you are doing in this query. But as I said, the variable will be empty. So the is_mutual will be ignored.

ah, that was the detail i needed :bulb:

Thanks!