Jerad Rose
Jerad Rose

Reputation: 15513

Adding FunctionScore/FieldValueFactor to a MultiMatch query

We've got a pretty basic query we're using to allow users to provide a query text, and then it boosts matches on different fields. Now we want to add another boost based on votes, but not sure where to nest the FunctionScore in.

Our original query is:

var results = await _ElasticClient.SearchAsync<dynamic>(s => s
    .Query(q => q
        .MultiMatch(mm => mm
            .Fields(f => f
                .Field("name^5")
                .Field("hobbies^2")
            )
            .Query(queryText)
        )
    )
);

If I try to nest in FunctionScore around the MultiMatch, it basically ignores the query/fields, and just returns everything in the index:

var results = await _ElasticClient.SearchAsync<dynamic>(s => s
    .Query(q => q
        .FunctionScore(fs => fs
            .Query(q2 => q2
                .MultiMatch(mm => mm
                    .Fields(f => f
                        .Field("name^5")
                        .Field("hobbies^2")
                    )
                    .Query(queryText)
                )
            )
        )
    )
);

My expectation is that since I'm not providing a FunctionScore or any Functions, this should basically do the exact same thing as above. Then, just adding in FunctionScore will provide boosts on the results based on the functions I give it (in my case, boosting based on the votes field just FieldValueFactor).

The documentation around this is a little fuzzy, particularly with certain combinations, like MultiMatch, FunctionScore, and query text. I did find this answer, but it doesn't cover when including query text.

I'm pretty sure it boils down to my still foggy understanding of how Elastic queries work, but I'm just not finding much to cover the (what I would think is a pretty common) scenario of:

Upvotes: 1

Views: 281

Answers (1)

Russ Cam
Russ Cam

Reputation: 125528

Your function_score query is correct, but the reason that you are not seeing the results that you expect is because of a feature in NEST called conditionless queries. In the case of a function_score query, it is considered conditionless when there are no functions, omitting the query from the serialized form sent in the request.

The easiest way to see this is with a small example

private static void Main()
{
    var defaultIndex = "my-index";
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var settings = new ConnectionSettings(pool, new InMemoryConnection())
        .DefaultIndex(defaultIndex)
        .DisableDirectStreaming()
        .PrettyJson()
        .OnRequestCompleted(callDetails =>
        {
            if (callDetails.RequestBodyInBytes != null)
            {
                Console.WriteLine(
                    $"{callDetails.HttpMethod} {callDetails.Uri} \n" +
                    $"{Encoding.UTF8.GetString(callDetails.RequestBodyInBytes)}");
            }
            else
            {
                Console.WriteLine($"{callDetails.HttpMethod} {callDetails.Uri}");
            }

            Console.WriteLine();

            if (callDetails.ResponseBodyInBytes != null)
            {
                Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                         $"{Encoding.UTF8.GetString(callDetails.ResponseBodyInBytes)}\n" +
                         $"{new string('-', 30)}\n");
            }
            else
            {
                Console.WriteLine($"Status: {callDetails.HttpStatusCode}\n" +
                         $"{new string('-', 30)}\n");
            }
        });

    var client = new ElasticClient(settings);

    var queryText = "query text";

    var results = client.Search<dynamic>(s => s
        .Query(q => q
            .FunctionScore(fs => fs
                .Query(q2 => q2
                    .MultiMatch(mm => mm
                        .Fields(f => f
                            .Field("name^5")
                            .Field("hobbies^2")
                        )
                        .Query(queryText)
                    )
                )
            )
        )
    );
}

which emits the following request

POST http://localhost:9200/my-index/object/_search?pretty=true&typed_keys=true 
{}

You can disable the conditionless feature by marking a query as Verbatim

var results = client.Search<dynamic>(s => s
    .Query(q => q
        .FunctionScore(fs => fs
            .Verbatim() // <-- send the query *exactly as is*
            .Query(q2 => q2
                .MultiMatch(mm => mm
                    .Fields(f => f
                        .Field("name^5")
                        .Field("hobbies^2")
                    )
                    .Query(queryText)
                )
            )
        )
    )
);

This now sends the query

POST http://localhost:9200/my-index/object/_search?pretty=true&typed_keys=true 
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query": "query text",
          "fields": [
            "name^5",
            "hobbies^2"
          ]
        }
      }
    }
  }
}

Upvotes: 1

Related Questions