Binding error when generating average

I’m trying to generate an average for each player from an array that contains a match score in each position, but I get the following error:
“Field binding must be a pure unary function.”

I already created this type of index with another format without problems. But in this case the error response is as if I had a function with more than one input variable.
I couldn’t identify it.

I’m using this fql to create the index:

  q.CreateIndex({
    name: "average_attacks_per_match_by_season_id",
    source: {
      collection: q.Collection("statistics"),
      fields: {
        attacks: q.Query(
            q.Lambda(
            "doc",
            q.Let(
              {
                per_match: q.Add(  
                  q.Select(['data', 'scouts', 'attacks_per_match'],
                    q.Var('doc')
                  )
                )
              },
              q.If(
                q.IsEmpty( q.Var('per_match') ),
                0,
                q.Round(
                  q.Mean(
                    q.Var('per_match'),
                  ),
                  3
                )
              )
            )
          )
        )
      }
    },
    terms: [{ field: ["data", "season_id"] }],
    values: [
      { binding: "attacks", reverse:true },
      { field: ["data", "player_id"] },
      { field: ["data", "team_id"] },
    ]
  })

Hi @Roco !

The error Field binding must be a pure unary function occurs when a binding uses a function that could imply/use reads or writes. The determination is shallow, meaning that the query processor doesn’t know for sure whether reads or writes definitely will take place, just that they could.

As listed in the documentation, Mean is a function that cannot be used in a binding.

Looking at your query, it doesn’t look like Mean would do anything for you. The per_match variable gets the result of calling Add on the data.scouts.attacks_per_match field, so it is a single numeric value. If you call Mean on a single value, you get the error:

Array, Set, or Page expected, Integer provided.

That mention of a Set is why Mean can’t be used in a binding.

Anway, even if Mean(1) worked, the answer would always be the input value, since there is only one value to consider.

Instead of using a binding, you could compute the mean whenever you update the attacks_per_match field, and create your index on that value.

Hi @ewan

sorry, I ended up copying another test I was taking.
there is no “add” there, only the select and then I was using the mean to calculate the average.

The add that stayed there, was from when in the “let” I created two variables

  • “dividend” with the sum of the items in the attacks_per_match array
  • “divisor” number obtained using count to get the total items in the array.

And I also got the same error ( Field binding must be a pure unary function).

This was the other fql I tested (without using “Mean”):

q.CreateIndex({
  name: "average_attacks_per_match_by_season_id",
  source: {
    collection: q.Collection("statistics"),
    fields: {
      attacks: q.Query(
          q.Lambda(
          "doc",
          q.Let(
            {
              dividend: q.Add(
                q.Select(['data', 'scouts', 'attacks_per_match'],
                  q.Var('doc')
                )
              ),
              divisor: q.Count(
                q.Select(['data', 'scouts', 'attacks_per_match'],
                  q.Var('doc')
                )
              ),
              
            },
            q.If(
              q.Equals(q.Var('divisor'), 0),
              0,
              q.Round(
                q.Divide(
                  q.Var('dividend'),
                  q.ToDouble(
                    q.Var('divisor')
                  )
                ),
                3
              )
            )
          )
        )
      )
    }
  },
  terms: [{ field: ["data", "season_id"] }],
  values: [
    { binding: "attacks", reverse:true },
    { field: ["data", "player_id"] },
    { field: ["data", "team_id"] },
  ]
})

But I think I’ll do as you mentioned. I’m going to create another field in the doc already keeping the calculated average and create an index in it.

However, as a way of learning, how should you proceed to calculate this average when creating the index?

Anyway, I’ll keep it the way you mentioned, it should be less expensive too.

And I also got the same error ( Field binding must be a pure unary function).

That’s due to the use of Count in your second example. Count can operate on sets, which implies potential reads, and so cannot be used in bindings.

However, as a way of learning, how should you proceed to calculate this average when creating the index?

My suggestion was to calculate the average when create/updating the document. Once you have an average field in the document, you can index that, like any other field.

The calculation would either be in your application logic, or you could use a UDF. UDFs aren’t limited like bindings are, since they don’t execute in the context of indexing, so you can use Mean, Count, Reduce, etc. and compute almost anything you want (up to the transaction duration limit).

Yes, I understand.
But I imagine your first suggestion will be more performative. I used this way.

Good to know about the use of count issue. Thanks

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.