Search code examples
c#elasticsearchnest

Search based on number of words in term using nest elastic


I have a website e-commerce. User can search for products. Each product have at list "name", "brand", "category". If user searching for "Tommy t-shirt men" then only mens tommy t-shirts should be returned as result, no results with single word tommy or t-shirt should be returned. If user searching for "Tommy" then all results with word tommy should be returned. If user searching for "Tommy men" only tommy mens products should be returned, not with single match word tommy or single match word men.

My code is looks like that:

public ISearchResponse<Models.Product> Search(string term, int minMatch)
{
    var response = client.Search<Models.Product>(search => search
            .Query(q => q.Bool(b => b.Should(
            s => s.Match(m => m.Query(term).Field(f => f.ProductName).Boost(5).Fuzziness(Fuzziness.EditDistance(0))),
            s => s.Match(m => m.Query(term).Field(f => f.Brand).Boost(15).Fuzziness(Fuzziness.EditDistance(0))),
            s => s.Match(m => m.Query(term).Field(f => f.Category).Boost(10).Fuzziness(Fuzziness.EditDistance(0)))
            ).MinimumShouldMatch(minMatch))));

            return response;
}

public ISearchResponse<Models.Product> Read(string term)
{
    var fixedInput = Regex.Split(term, @"[^\p{L}]*\p{Z}[^\p{L}]*");
    int minMatch;

    if (fixedInput.Count() > 1) minMatch = 2; 
    else  minMatch = 1; 

    var results = Search(term, minMatch);

    if (!results.Documents.Any() && minMatch.Equals(2))
    {
        results = Search(term, 1);
    }

    return results;
}

If i'm searching for "Tommy men", first results is "Tommey men" but others is "Diesel men", "Boss men", how to filter out results with one word match if search term have more then one word.


Solution

  • Do you mean that "tommy" != "Tommy" in the below sentence?

    no results with single word tommy or t-shirt should be returned. If user searching for "Tommy" then all results with word tommy should be returned.

    For the rest of the examples, you can use multi-match query with cross_fields type.

    Sample app:

        public class Document
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Brand { get; set; }
            public string Category { get; set; }
    
            public override string ToString() => $"Id: {Id} Name: {Name} Brand: {Brand} Category: {Category}";
        }
    
        static async Task Main(string[] args)
        {
            var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
            var connectionSettings = new ConnectionSettings(pool);
            connectionSettings.DefaultIndex("documents");
    
            var client = new ElasticClient(connectionSettings);
    
            var deleteIndexResponse = await client.Indices.DeleteAsync("documents");
            var createIndexResponse = await client.Indices.CreateAsync("documents", d => d
                .Map(m => m.AutoMap<Document>()));
    
            var indexDocument = await client
                .IndexDocumentAsync(new Document {Id = 1, Brand = "Tommy", Category = "men"});
            var indexDocument2 = await client
                .IndexDocumentAsync(new Document {Id = 2, Brand = "Diesel", Category = "men"});
            var indexDocument3 = await client
                .IndexDocumentAsync(new Document {Id = 3, Brand = "Boss", Category = "men"});
    
            var refreshAsync = client.Indices.RefreshAsync();
    
            var query = "Tommy";
            var searchResponse = await Search(client, query);
            PrintResults(query, searchResponse);
    
            query = "Tommy men";
            searchResponse = await Search(client, query);
            PrintResults(query, searchResponse);
    
            query = "men";
            searchResponse = await Search(client, query);
            PrintResults(query, searchResponse);
        }
    
        private static async Task<ISearchResponse<Document>> Search(ElasticClient client, string query)
        {
            var searchResponse = await client.SearchAsync<Document>(s => s.Query(q => q
                .MultiMatch(mm => mm
                    .Fields(f => f.Fields(ff => ff.Brand, ff => ff.Category, ff => ff.Name))
                    .Query(query)
                    .Type(TextQueryType.CrossFields)
                    .MinimumShouldMatch("100%"))));
            return searchResponse;
        }
    
        private static void PrintResults(string query, ISearchResponse<Document> searchResponse)
        {
            Console.WriteLine($"query: {query}");
            Console.WriteLine(searchResponse.Total);
            Console.WriteLine($"results: ");
            searchResponse.Documents.ToList().ForEach(Console.WriteLine);
            Console.WriteLine();
        }
    }
    

    Prints:

    query: Tommy
    found: 1
    Id: 1 Name:  Brand: Tommy Category: men
    
    query: Tommy men
    found: 1
    Id: 1 Name:  Brand: Tommy Category: men
    
    query: men
    found: 3
    Id: 1 Name:  Brand: Tommy Category: men
    Id: 2 Name:  Brand: Diesel Category: men
    Id: 3 Name:  Brand: Boss Category: men
    

    Hope that helps.