Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elasticsearch suggestions with filter

I have a requirement to provide suggestions which is working fine but I also need to filter the suggestions by another field in the document.

Is this possible to achieve? As long as I have figured out, Elasticsearch can not do this. Any alternative ideas?

public async Task<ISuggestResponse> Suggest(string index, string projectId, string field, string text)
        {
            var suggestResponse = await _client.SuggestAsync<TDocument>(s => s
                                                                            .Index(index)
                                                                            .Completion("suggest", c => c
                                                                                    .Text(text)
                                                                                    .Context(con => con.Add("projectId", projectId))
                                                                                    .Field(field)
                                                                                    .Size(20)
                                                                                )
                                                                    );

        return suggestResponse;
    }

-----------Update--------------------

ElasticsearchConfig.cs

client.Map<Component>(d => d
        .Properties(props => props
            .String(s => s
                .Name("name"))
            .Completion(c => c
                .Name("componentSuggestion")
                .Analyzer("simple")
                .SearchAnalyzer("simple")
                .Context(context => context
                    .Category("projectId", cat => cat
                    .Field(field => field.ProjectId)))
                .Payloads()))
        .Properties(props => props.String(s => s.Name("id").NotAnalyzed()))
        .Properties(props => props.String(s => s.Name("projectId").NotAnalyzed())));

enter image description here

like image 287
Chirdeep Tomar Avatar asked Jul 27 '16 20:07

Chirdeep Tomar


2 Answers

The Context Suggester extends the Completion Suggester to provide an element of basic filtering either on a Category or Geolocation. This may be sufficient for your purposes on it's own.

An alternative approach that you may want to take is to use the Context Suggester to provide search-as-you-type suggestions, indexing the id of each document within the payload of the completion type mapping; then use the ids returned within the payloads to search for documents, applying your additional filtering at this point and return only the document ids that match the filtering. Finally, use these document ids to get the suggestions from the original suggest response.

EDIT:

Here's a complete example of working with the Context Suggester

void Main()
{
    var componentsIndex = "components";
    var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    var settings = new ConnectionSettings(connectionPool)
        // use component index when working with
        // Component Poco type
        .InferMappingFor<Component>(m => m
            .IndexName(componentsIndex)
        );

    var client = new ElasticClient(settings);

    // make this example repeatable, so delete index if
    // it already exists
    if (client.IndexExists(componentsIndex).Exists)
        client.DeleteIndex(componentsIndex);

    // for example purposes, only use one shard and no replicas
    // NOT RECOMMENDED FOR PRODUCTION
    client.CreateIndex(componentsIndex, c => c
        .Settings(s => s
            .NumberOfShards(1)
            .NumberOfReplicas(0)
        )
    );

    client.Map<Component>(d => d
        .Index(componentsIndex)
        // infer mapping of fields from property of the POCO.
        // This means we don't need to explicitly specify all 
        // mappings in .Properties()
        .AutoMap()
        // Now, override any inferred mappings from automapping
        .Properties(props => props
            .Completion(c => c
                .Name(n => n.ComponentSuggestion)
                .Context(context => context
                    .Category("projectId", cat => cat
                        .Field(field => field.ProjectId)
                    )
                )
                .Payloads()
            )
            .String(s => s
                .Name(n => n.Id)
                .NotAnalyzed()
            )
            .String(s => s
                .Name(n => n.ProjectId)
                .NotAnalyzed()
            )
        )
    );

    var components = new[] {
        new Component
        {
            Id = "1",
            Name = "Component Name 1",
            ComponentSuggestion = new CompletionField<object>
            {
                Input = new [] { "Component Name 1" },
                Output = "Component Name 1"
            },
            ProjectId = "project_id"
        },
        new Component
        {
            Id = "2",
            Name = "Component Name 2",
            ComponentSuggestion = new CompletionField<object>
            {
                Input = new [] { "Component Name 2" },
                Output = "Component Name 2"
            },
            ProjectId = "project_id_2"
        }
    };

    // index some components with different project ids
    client.IndexMany(components);

    // refresh the index to make the newly indexed documents available for
    // search. Useful for demo purposes but,
    // TRY TO AVOID CALLING REFRESH IN PRODUCTION     
    client.Refresh(componentsIndex);

    var projectId = "project_id";

    var suggestResponse = client.Suggest<Component>(s => s
        .Index(componentsIndex)
        .Completion("suggester", c => c
            .Field(f => f.ComponentSuggestion)
            .Text("Compon")
            .Context(con => con.Add("projectId", projectId))
            .Size(20)
        )
    );

    foreach (var suggestion in suggestResponse.Suggestions["suggester"].SelectMany(s => s.Options))
    {
        Console.WriteLine(suggestion.Text);
    }
}

public class Component
{
    public string Id { get; set; }

    public string Name { get; set; }

    public string ProjectId { get; set; }

    public CompletionField<object> ComponentSuggestion { get; set; }
}

This yields only one suggestion, Component Name 1, based on the context of projectId being "project_id"

like image 94
Russ Cam Avatar answered Oct 02 '22 08:10

Russ Cam


Instead of the completion suggester, you should be using the context suggester whose goal is to allow you to specify additional category or geo-location context for your completion suggester.

If I'm not mistaken, NEST provides the context suggester as part of the completion suggester through the Context property.

public async Task<ISuggestResponse> Suggest(string index, string field, string text)
    {
        var suggestResponse = await _client.SuggestAsync<TDocument>(s => s
              .Index(index)
              .Completion("suggest", c => c
                    .Text(text)
                    .Context(con => con.Add("your_field", "text"))
                    .Field(field)
                    .Size(20)
              )
        );

        return suggestResponse;
    }

You also need to change the completion field in your mapping in order to declare the context.

like image 29
Val Avatar answered Oct 02 '22 07:10

Val