Filtering by Relation?

Hi there!

I’m struggling a bit with a query I’m trying to write. The idea behind it seems very simple to me, yet after reading the docs and playing around with Dgraph for a bit, I’m still not able to accomplish the following:

With a schema something like

name: string .
relation: [uid] @reverse .

and this data:

{
    set {
        _:one <name> "One" .
        _:one <relation> _:ten (name="child") .
        _:two <name> "Two" .
        _:two <relation> _:ten (name="child") .
        _:ten <name> "Ten" .
        _:two <relation> _:eleven (name="child") .
        _:eleven <name> "Eleven" .
        _:three <name> "Three" .
        _:three <relation> _:ten (name="child") .

    }
}

(The relation name is stored on a facet since users should be able to define custom relations in my application)

How would I find nodes that have a certain relation to all of a list of uids?

The only thing I could come up with is

query {
  q(func: has(name)) @filter(uid_in(~relation, "0x2744") AND uid_in(~relation, "0x2741")) {
    uid
    name
  }
}

But I want something more dynamic where I can pass a list of uids as variable, instead of hardcoding a query like that.
So with my example data, I would expect the query

  • called with the uids of [one, two, three] to return ten
  • called with the uids of [one, two] to return ten
  • called with the uids of [two] to return ten and eleven

I hope my explanation makes sense.
Can I write such a query with Dgraph?

Thanks in advance

At first, using cascade is a bit tricky. But it is useful in many situations.

{
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter(eq(name,["One", "Two", "Three"]) and not eq(name,"Two"))
  }
}

OR (If you know that the set of nodes you need has more than 1 relationship).

{
  q(func: has(name)) @filter(gt(count(~relation), 1)) @cascade  {
    uid
    name
    ~relation @filter(eq(name,["One", "Two", "Three"]))
  }
}
{
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter(eq(name,["One", "Two"]) and not eq(name,"Two"))
  }
}
 { q(func: has(name)) @filter(eq(count(~relation), 1) ) @cascade  {
    uid
    name
    ~relation @filter(eq(name,"Two") )
  }
}

In the last example, as both Ten and Eleven are related to Node “Two”. I needed to set a parameter to make sure it has only a single relationship and no more than one. Using eq (count (~ relation), 1). It’s kind of a trick, but avoids returning unwanted sets. You can use @filter(gt(count(~relation), 1) (Greater than) if you need tho.

Thanks for your thorough response @MichelDiz!
Seems like I failed to explain what I need :confused:

First, I’m trying to query by uid, not name. Could those simply be swapped in your queries?
Your queries also make assumptions about the data if I’m not mistaken.

Would it be possbile to build one universal query that does find nodes that have a certain relation to all nodesof a list of uids, making no assumptions about the available data (e.g. any node could have any amount of relations to any node, and you know nothing about the data except the schema)?

Yes. In that case you gonna use uid func instead of uid_in.

e.g:

~relation @filter(uid(0x2744) AND uid(0x2741) ) #you can use OR too.

I used your sample and tried to interpret your text.

I think if you use cascade and uid func you can accomplish this.

Are you sure this would work?
In my testing this doesn’t return anything.
I think that’s because it searches for a relation that is pointing to two nodes, which isn’t possible. There can’t be a single relation that matches @filter(uid(0x2744) AND uid(0x2741) )

Do I miss something?

yep. Try these queries.

{
  # q1(func: has(relation)){
  #   uid
  #   name
  # }
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter( uid("0xc0e32") AND uid("0xc0e34") OR uid("0xc0e36"))
  }
}
{
  # q1(func: has(relation)){
  #   uid
  #   name
  # }
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter( uid("0xc0e32") OR uid("0xc0e34") AND uid("0xc0e36")) 
  }
}
{
  # q1(func: has(relation)){
  #   uid
  #   name
  # }
  q(func: has(name)) @filter(eq(count(~relation), 1)) @cascade  {
    uid
    name
    ~relation @filter(uid("0xc0e34"))
  }
}

All of the queries you just posted try to match just one uid, by WONT_MATCH AND WONT_MATCH OR WILL_MATCH.

So for example

would also match when the node doesn’t have a relation with the first two uids

The point was

The same queries done before

but the func swapped from eq(name, “x”) to uid(x).

{
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter(uid(0xc0e32,0xc0e34,0xc0e36) and not uid(0xc0e34))
  }
}
{
  q(func: has(name)) @filter(gt(count(~relation), 1)) @cascade  {
    uid
    name
    ~relation @filter(uid(0xc0e32,0xc0e34,0xc0e36))
  }
}
{
  q(func: has(name)) @cascade  {
    uid
    name
    ~relation @filter(uid(0xc0e32,0xc0e34) and not uid(0xc0e34))
  }
}
{ 
  q(func: has(name)) @filter(eq(count(~relation), 1) ) @cascade  {
    uid
    name
    ~relation @filter(uid(0xc0e34))
  }
}

That’s the values.

{
  "data": {
    "q1": [
      {
        "uid": "0xc0e32",
        "name": "One"
      },
      {
        "uid": "0xc0e34",
        "name": "Two"
      },
      {
        "uid": "0xc0e36",
        "name": "Three"
      }
    ]
  }
}

Hey Emil,

I think you can create this logic with use of query blocks like this:

{
  Q1 as var(func: has(name)) @cascade {
    uid
    name
    ~relation @filter(eq(name,"One"))
  }
  Q2 as var(func: has(name)) @cascade {
    uid
    name
    ~relation @filter(eq(name,"Two"))
  }
  Q3 as var(func: has(name)) @cascade {
    uid
    name
    ~relation @filter(eq(name,"Three"))
  }
  result(func: has(name)) @filter(uid(Q1) AND uid(Q2) AND uid(Q3)) { 
    uid
    name
  }
}

You could programmatically create this query for any number of arguments without any need for hardcoding, and since dgraph processes query blocks in parallel, it should return results quite fast even for bigger queries; however I wonder, if this can be done in an more elegant/efficient way.

Thanks @ppp225!

That works. I’ll do some performance testing comparing it to my version in the op

Yep, was just thinking that maybe it would be possible to not have to create a new query, but I guess that’s how you do it :slight_smile:

Thanks