Comparing two predicate counts in a filter

Trying to find all nodes that have the same number of uids in SecondList as they have in FirstList.

query {
  nodes(func:type("Node"))
    @filter(eq(count(FirstList), count(SecondList))
    @cascade
  {
    # FirstList {
    #   uid
    # }
    # SecondList {
    #  uid
    # }
  }
}

But there is an error from dgraph saying “Multiple functions as arguments not allowed”.

Is there some other way that this can be achieved?

Have you tried using variables to store the count result, and using it in the filter?

Sure, but it has the same problem since the eq function has two val() functions inside…

query {
  ID as var(func:type("Node")) {
    firstCount as count(Links @filter(type("Node")))
    secondCount as count(Links @filter(ge(count(Links),1)))
  }
  nodes(func:uid(ID))
      @filter(eq(val(firstCount), val(secondCount)))
  {
    uid
  }
}

Same error: Multiple functions as arguments not allowed.

None of those syntaxes doesn’t exists in Graphql+- as you can see in the docs https://docs.dgraph.io/query-language/#inequality

Try something like this

{
  var(func:type("Node"))  {
   F as  FirstList
   S as  SecondList
  }
  
  q(func: uid(F)) @filter(uid(S)) {
    uid
    expand(_all_)
  }
}

What gonna happen is, the query block will expand all nodes in FirstList, and then filter to show only those nodes that exists in both edges.

Assuming that:

FirstList has:
0x20
0x21
0x23
0x24
0x25
0x26

SecondList has:
0x21
0x23
0x25
0x26

The result for the query should be:

0x21
0x23
0x25
0x26

You can also use q(func: uid(F)) @filter(NOT uid(S))
The result for the query should be:

0x20
0x24

This is not quite the same thing as I asked. I don’t care about the actual nodes, I only care about the amount of nodes.

All I care is to find UIDs whose list lengths are the same, without caring about the content of these lists.

For example, given a genealogy tree find all people who have the same number of siblings as their number of children. Naturally their brothers and sisters are not the same people as their children. And the query would find people who have 4 siblings and 4 children, as well as those who only have 1 sibling and 1 child. But not those who have 3 siblings and 2 children, or 5 children but 1 sibling.

Hum, I was trying to accomplish what I understood from your queries. What the syntaxes would mean. But as you explained it better, I can see that the queries are far from it. And I can say that is not possible to do it in a practical way. Bellow follows a kind of “hack” way to do comparisons and getting a meaningful result from it (Like “true/false”).

It is “hacky”, which means it can’t have the desired result in other scenarios and it abuses from the query system.

I’ve used the other sample of yours to do this example.

{
  var(func: uid(0x7)) {
    A as count(Links) #It has 2 links
  }
  var(func: uid(0x6)) {
    B as count(Links) #It has 1 link
  }

  var() {
    G as sum(val(A))
    T as sum(val(B))
  }
  me() {
    result: math( G > T )
  }
}

Result

{
  "data": {
    "me": [
      {
        "result": true
      }
    ]
  }
}

And this needs a deeper look and thought to answer.

For curiosity and to better understand the fundamentals, why does dgraph not support two functions:

What is preventing dgraph from being able to process this? If it can evaluate a function on one side, shouldn’t it also be able to evaluate a function on the other side of an equation? Maybe this is a little deeper subject than the OP, sorry.

A couple of years passed and I am interested in this feature. As amaster says this would be a normal aproach:

@filter(eq(val(firstCount), val(secondCount)))

but this does not work, and I have to do something like:

var() { 
    equal as math(firstCount- secondCount)
}
query(func:...) @filter(eq(val(equal),0)) {
    uid
}

Things like this make DQL feel superhacky sometimes :frowning_face:

In my use case I want to fetch n nodes of type A related to nodes of type B and get only the nodes of type b that are related to each of the n type A nodes, to do that I already use the variable propagation thing that also feel extremely counter intuitive but works like a charm:

  var(func: type(A)) @filter(eq(name,"foo")) {  #can be 1,2 or n
      A as uid
      Acount as count(uid)
      paths as math(1)
      a as A.linktoB {
        ticks as math(paths) #the variable propagation math trick
      }
  }

 var() { 
     equal as math(ticks - Acount)
 } #if acount == ticks means that the nodeB is related to all the nodesA
  
 validB as var(func: uid(a),first:10) @filter(eq(val(equal),0)) { uid }  
  
 output(func: uid(A)) {
     A.name
     A.linktoB @filter(uid(validB)) {
	   B.name
     }
 }

It works, but the amaster aproach would make this more readable and easier for everybody.
What do you think?

I think it’s a fair request. Let me add it to our backlog. As the two variables are both UID maps we should be able to evaluate this filter condition easily.

@kesor feel free to add comments and data example to the enhancement request. Thanks.

2 Likes

I created a parent node with groupA and groupB relationships.
Finding the parents with the same number of groupA and groupB relationships can be achieved with

{
    var(func: type(Parent)) {
      a as count(groupA)
      b as count(groupB)
      c as math(b-a)
     }
    r(func:uid(c))   @filter(eq(val(c),0)){
        name val(a) val(b) val(c)
    }
}

Could you tell me if this approach is working for you ? If not, what’s the detail in the use case preventing you from using this simple counting approach ?