Adrian Carr
Adrian Carr

Reputation: 3133

Nest Elasticsearch- searching front of some fields and back of others

I have a case where I need a partial match on the first part of some properties (last name and first name) and a partial match on the end of some other properties, and I'm wondering how to add both analyzers. For example, if I have the first name of "elastic", I can currently search for "elas" and find it. But, if I have an account number of abc12345678, I need to search for "5678" and find all account numbers ending in that, but I can't have a first name search for "stic" find "elastic".

Here's a simplified example of my Person class:

public class Person
{       
    public string AccountNumber { get; set; }

    [ElasticProperty(IndexAnalyzer = "partial_name", SearchAnalyzer = "full_name")]
    public string LastName { get; set; }
    [ElasticProperty(IndexAnalyzer = "partial_name", SearchAnalyzer = "full_name")]
    public string FirstName { get; set; }       
}

Here's the relevant existing code where I create the index, that currently works great for searching the beginning of a word:

//Set up analyzers on some fields to allow partial, case-insensitive searches.
var partialName = new CustomAnalyzer
{
    Filter = new List<string> { "lowercase", "name_ngrams", "standard", "asciifolding" },
    Tokenizer = "standard"
};

var fullName = new CustomAnalyzer
{
    Filter = new List<string> { "standard", "lowercase", "asciifolding" },
    Tokenizer = "standard"
};

var result = client.CreateIndex("persons", c => c
                .Analysis(descriptor => descriptor
                    .TokenFilters(bases => bases.Add("name_ngrams", new EdgeNGramTokenFilter
                    {
                        MaxGram = 15, //Allow partial match up to 15 characters. 
                        MinGram = 2, //Allow no smaller than 2 characters match
                        Side = "front"
                    }))
                    .Analyzers(bases => bases
                        .Add("partial_name", partialName)
                        .Add("full_name", fullName))
                    )
                    .AddMapping<Person>((m => m.MapFromAttributes()))
                );

It seems like I could add another EdgeNGramTokenFilter, and make the Side = "back", but I don't want the first and last name searches to match back side searches. Can someone provide a way to do that? Thanks, Adrian

Edit

For completeness, this is the new decorator on the property that goes with the code in the accepted answer:

[ElasticProperty(IndexAnalyzer = "partial_back", SearchAnalyzer = "full_name")]
public string AccountNumber { get; set; }

Upvotes: 0

Views: 160

Answers (1)

Val
Val

Reputation: 217314

You need to declare another analyzer (let's call it partialBack) specifically for matching from the back but you can definitely reuse the existing edgeNGram token filter, like this:

var partialBack = new CustomAnalyzer
{
    Filter = new List<string> { "lowercase", "reverse", "name_ngrams", "reverse" },
    Tokenizer = "keyword"
};
...
                .Analyzers(bases => bases
                    .Add("partial_name", partialName)
                    .Add("partial_back", partialBack))
                    .Add("full_name", fullName))
                )

The key here is the double use of the reverse token filter.

The string (abc12345678) is

  • first lowercased (abc12345678),
  • then reversed (87654321cba),
  • then edge-ngramed (87, 876, 8765, 87654, 876543, ...)
  • and finally the tokens are reversed again (78, 678, 5678, 45678, 345678, ...).

As you can see, the result is that the string is tokenized "from the back", so that a search for 5678 would match abc12345678.

Upvotes: 3

Related Questions