5

Short version:

I would like to write an elastic search query using Nest to get the full indexed items (ContentIndexables in my case as my custom type) which have been indexed. The query is subject to a term query of [some string] + * (i.e. String.StartsWith() where [some string] may or may not contain spaces.

This is different to CompletionSuggester since I need to retrieve the full object and not string suggestions.

What I've tried so far:

When I query for a text without spaces, the desired output is returned using the code below. If my search term however contains spaces, it doesn't return the expected results.

Here's how I search the fields:

var searchResults = _client.Search<ContentIndexable>(
            body =>
            body
                .Index(indexName)
                .Query(
                    query =>
                    query.QueryString(
                        qs => qs.
                                  OnFields(f => f.Title, f => f.TextContent)
                                  .Query(searchTerm + "*"))));

And this is a unit test that demonstrates how to reproduce the problem:

indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
        {
            ContentId = Guid.NewGuid(),
            TextContent = "Some description",
            Title = "title"
        });

        indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
        {
            ContentId = Guid.NewGuid(),
            TextContent = "Some description",
            Title = "title that is long"
        });

        indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
        {
            ContentId = Guid.NewGuid(),
            TextContent = "Some description",
            Title = "title that likes"
        });

        indexService.IndexUserItemsSync(testGuid, IndexType.submission, new ContentIndexable
        {
            ContentId = Guid.NewGuid(),
            TextContent = "Some description",
            Title = "titlethat"
        });

        var searchResult = indexService.SearchUserItems(testGuid, IndexType.submission, 10, "title");
        Assert.IsNotNull(searchResult);
// this one works
        Assert.AreEqual(4, searchResult.Count());

        var searchResult2 = indexService.SearchUserItems(testGuid, IndexType.submission, 10, "title that");
        Assert.IsNotNull(searchResult2);
// this one does not!!! searchREsult2.Count() evaluates to 0
        Assert.AreEqual(2, searchResult2.Count());

As you can see, then I enter "title that", the search comes back empty instead of the two rows I would expect to return.

Update: Some more information: I create an index on my type ContentIndexable:

public class ContentIndexable : IIndexable
{
    public Guid ContentId { get; set; }
    public string Title { get; set; }
    public string TextContent { get; set; }
}

With this code:

_client.CreateIndex(
    indexName,
    descriptor =>
    descriptor.AddMapping<ContentIndexable>(
        m => m.Properties(
            p => p.Completion(s => s
                                       .Name(n => n.Title)
                                       .IndexAnalyzer("standard")
                                       .SearchAnalyzer("standard")
                                       .MaxInputLength(30)
                                       .Payloads()
                                       .PreserveSeparators()
                                       .PreservePositionIncrements())
                     .Completion(s => s.Name(n => n.TextContent)
                                          .IndexAnalyzer("standard")
                                          .SearchAnalyzer("standard")
                                          .MaxInputLength(50)
                                          .Payloads()
                                          .PreserveSeparators()
                                          .PreservePositionIncrements())
                 )));

I even tried to escape the whitespace both when I index or when I query with string.Replace(" ", @"\ ") but that didn't help.

Changing the search type to wild card didn't help either:

var searchResults = _client.Search<ContentIndexable>(
            body =>
            body
                .Index(indexName)
                .Query(
                    query => query.Wildcard(qd => qd.OnField(f => f.Title).Value(searchTerm + "*"))));

Does anyone know what I'm doing wrong?

Please note that my CompletionSuggester version works with spaces but unfortunately only returns strings. I need to get the complete item out in order to fetch the ContentId. MY CompletionSuggester implementation:

public IEnumerable<string> GetAutoCompleteSuggestions(Guid userId, IndexType indexType, int size, string searchTerm)
    {
        string indexName = getIndexName(indexType, userId);

        var result = _client.Search<ContentIndexable>(
            body => body.Index(indexName)
                        .SuggestCompletion("content-suggest" + Guid.NewGuid(),
                                           descriptor => descriptor
                                                             .OnField(t => t.Title)
                                                             .Text(searchTerm)
                                                             .Size(size)));

        if (result.Suggest == null)
        {
            return new List<string>();
        }

        return (from suggest in result.Suggest
                from value in suggest.Value
                from options in value.Options
                select options.Text).Take(size);
    }

I know I can take the suggestions, get the full value out (which will result in the two items I'm expecting) and then do a full term match using my first method but that requires 2 separate calls into ElasticSearch (one for complete suggestor and the second one for the term query) but ideally I would like to do it without the round trip if possible.

Many thanks in advance,

kha
  • 19,123
  • 9
  • 34
  • 67
  • Are you trying to implement [completion suggester](http://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html) or rather your mapping is something like "maybe this will work"? – Rob Apr 21 '15 at 19:37
  • Hi @Rob. I have already done completion suggester but that's giving me back a list of strings. That list is correct and contains items with spaces correctly. What I'd like to do, is to do the same with a query that gives me the same result (with spaces) but instead of strings as suggestions, I need to get the entire indexed object out (ContentIndexable in my case). I am a complete newbie in ElasticSearch as you may have guessed so any suggestion would be appreciated. – kha Apr 22 '15 at 08:00

1 Answers1

4

This is example how you can deal with your problem for Title field.

Change your mapping to something like(or use MultiField, but I couldn't find option to map field as string and completion in the same time):

client.CreateIndex(indexName, i => i
    .AddMapping<ContentIndexable>(m => m
        .Properties(
            ps => ps
                .Completion(c => c.Name("title.completion")
                    .IndexAnalyzer("standard")
                    .SearchAnalyzer("standard")
                    .MaxInputLength(30)
                    .Payloads()
                    .PreserveSeparators()
                    .PreservePositionIncrements())
                .String(s => s.Name(x => x.Title).CopyTo("title.completion")))));

Change your SuggestCompletion to

var result = client.Search<ContentIndexable>(body => body
    .Index(indexName)
    .SuggestCompletion("content-suggest" + Guid.NewGuid(),
        descriptor => descriptor
            .OnField(t => t.Title.Suffix("completion"))
            .Text("title")
            .Size(10)));

and QueryString to

var searchResponse = client.Search<ContentIndexable>(body => body
    .Index(indexName)
    .Query(query => query
        .QueryString(
            qs => qs
                .OnFields(f => f.Title.Suffix("completion"))
                .Query("title tha" + "*")
                .MinimumShouldMatchPercentage(100))));

Problem with this solution is fact that we are storing data twice for Title field. This is why I mentioned earlier that would be great to use MultiField but I wasn't able to do this with the NEST.

Hope this helps.

Community
  • 1
  • 1
Rob
  • 9,664
  • 3
  • 41
  • 43
  • Thank you very much Rob. This worked with the exception that if my query string contained non alpha numeric characters, it would return nothing. Before I query, I remove those characters now and it works perfectly. – kha Apr 29 '15 at 18:27