adam0101
adam0101

Reputation: 31095

How can I influence the result score in Elasticsearch without filtering the result?

I want jobs that are close by to be higher in the search results, but whenever I try to alter the score based on distance, I'm getting zero results back. I don't want to filter jobs by distance at all, only alter how relevant the result is.

I tried:

        var response = await client.SearchAsync<JobIndex>(x => x
            .Index(nameof(JobIndex).ToLower())
            .Source(sf => sf.Includes(i => i.Fields(fields)))
            .From(query.Skip.GetValueOrDefault(0))
            .Size(query.Take.GetValueOrDefault(25))
            .Query(q =>
                q.Bool(b => b.MustNot(mustNotQueries))
                && q.Bool(b => b.Must(mustQueries))
                && q.Bool(b => b.Should(shouldQueries))
                && q.FunctionScore(c => c
                    .Query(s => s.MatchAll())
                    .Functions(scoreFunctions)
                )
              ), cancellationToken);

And also:

        var response = await client.SearchAsync<JobIndex>(x => x
            .Index(nameof(JobIndex).ToLower())
            .Source(sf => sf.Includes(i => i.Fields(fields)))
            .From(query.Skip.GetValueOrDefault(0))
            .Size(query.Take.GetValueOrDefault(25))
            .Query(q =>
                q.Bool(b => b.MustNot(mustNotQueries))
                && q.Bool(b => b.Must(mustQueries))
                && q.Bool(b => b.Should(shouldQueries))
              )
            .Query(q => q.FunctionScore(c => c
               .Query(s => s.MatchAll())
               .Functions(scoreFunctions)
            ))
            , cancellationToken);

But I get nothing back. The query works fine if I remove the parts that use "scoreFunctions". Here is the scoring function I'm using:

scoreFunctions.Add(new LinearGeoDecayFunction
    {
        Origin = new GeoLocation(result.Latitiude, result.Longitude),
        Field = Infer.Field<JobIndex>(g => g.LatLong),
        Scale = Distance.Miles(50),
        Decay = 0.5
    });

How do I write this query?

Upvotes: 2

Views: 153

Answers (1)

Rob
Rob

Reputation: 9979

You should move your main query into function score query and set boost mode to the sum as you want to add additional "points" for being close to the origin.

Here is the sample app. Tested with elasticsearch 7.4.0 and NEST 7.3.1

    static async Task Main(string[] args)
    {
        var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
        var connectionSettings = new ConnectionSettings(pool);
        connectionSettings
            .DefaultIndex("documents")
            //do not use in production
            .PrettyJson()
            .DisableDirectStreaming();

        var client = new ElasticClient(connectionSettings);

        await client.Indices.DeleteAsync("documents");

        await client.Indices.CreateAsync("documents", 
            d => d.Map(m => m.AutoMap<City>()));

        await client.IndexManyAsync(new List<City>
        {
            new City
            {
                Id = "1", Name = "Warszawa", Country = "Poland", Location = new GeoLocation(52.237049, 21.017532)
            },
            new City
            {
                Id = "2", Name = "Kraków", Country = "Poland", Location = new GeoLocation(50.049683, 19.944544)
            },
            new City
            {
                Id = "3", Name = "Wieliczka", Country = "Poland", Location = new GeoLocation(49.987061, 20.064796)
            },
            new City
            {
                Id = "4", Name = "Berlin", Country = "Germany", Location = new GeoLocation(52.520008, 13.404954)
            }
        });

        await client.Indices.RefreshAsync();

        var searchResponse = await client.SearchAsync<City>(s => s
            .Query(q => q.FunctionScore(fs =>
                fs
                    .Query(qq =>
                        qq.Match(mm => mm.Field(f => f.Country).Query("Poland")))
                    .BoostMode(FunctionBoostMode.Sum)
                    .Functions(new[]
                    {
                        new LinearGeoDecayFunction
                        {
                            Origin = new GeoLocation(
                                50.049683, 19.944544),
                            Field = Field<City>(
                                f => f.Location),
                            Scale = Distance.Kilometers(100),
                            Decay = 0.5
                        }
                    }))));

        foreach (var hit in searchResponse.Hits)
        {
            System.Console.WriteLine($"Name: {hit.Source.Name} Score: {hit.Score}");
        }
    }

    public class City
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Country { get; set; }
        [GeoPoint]
        public GeoLocation Location { get; set; }
    }
}

output

Name: Kraków Score: 1.3566749
Name: Wieliczka Score: 1.3013793
Name: Warszawa Score: 0.35667494

Hope that helps.

Upvotes: 2

Related Questions