Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

in NEST, how do I dynamically build a query from a list of terms?

Say my user provides a list of search terms which I've collected into an array/list, and now I want to combine those OR-wise into a NEST query using MatchPhrase. How would I do that? The code for a (single) search term would look something like this:

var search = client.Search<ElasticRequirement>(s => s
.Query(q =>
    q.MatchPhrase(m => m.OnField(f => f.Title).Query(term.ToLower()).Slop(slop))
    || q.MatchPhrase(m => m.OnField(f => f.Description).Query(text).Slop(slop))
    )
   .LowercaseExpandedTerms()
   .Explain()
   .Query(q => q.Fuzzy(f => f.PrefixLength(1).OnField(c => c.Title).OnField(c => c.Description)))
);

This is fine, but I need to apply that same MatchPhrase filter once for each provided search term. Any help much appreciated.

like image 834
see sharper Avatar asked Jan 28 '15 06:01

see sharper


1 Answers

You can use bool should expressions to build your query dynamically. I'll provide the complete solution below. Call BuildQuery() method with appropriate parameters.

ISearchResponse<ElasticRequirement> BuildQuery(IElasticClient client, IEnumerable<string> terms, int slop)
{
    return client.Search<ElasticRequirement>(s => s
        .Query(q => q
            .Bool(b => b
                .Should(terms.Select(t => BuildPhraseQueryContainer(q, t, slop)).ToArray())))
        .LowercaseExpandedTerms()
        .Explain()
        .Query(q => q.Fuzzy(f => f.PrefixLength(1).OnField(c => c.Title).OnField(c => c.Description))));
}

QueryContainer BuildPhraseQueryContainer(QueryDescriptor<ElasticRequirement> qd, string term, int slop)
{
    return qd.MatchPhrase(m => m.OnField(f => f.Title).Query(term.ToLower()).Slop(slop)) ||
        qd.MatchPhrase(m => m.OnField(f => f.Description).Query(term.ToLower()).Slop(slop));
}

For terms = {"term1", "term2", "term3"} and slop = 0, the Elasticsearch search JSON command that will get built by my code is as under:

{
  "explain": true,
  "query": {
    "bool": {
      "should": [
        {
          "bool": {
            "should": [
              {
                "match": {
                  "title": {
                    "type": "phrase",
                    "query": "term1",
                    "slop": 0
                  }
                }
              },
              {
                "match": {
                  "description": {
                    "type": "phrase",
                    "query": "term1",
                    "slop": 0
                  }
                }
              }
            ]
          }
        },
        {
          "bool": {
            "should": [
              {
                "match": {
                  "title": {
                    "type": "phrase",
                    "query": "term2",
                    "slop": 0
                  }
                }
              },
              {
                "match": {
                  "description": {
                    "type": "phrase",
                    "query": "term2",
                    "slop": 0
                  }
                }
              }
            ]
          }
        },
        {
          "bool": {
            "should": [
              {
                "match": {
                  "title": {
                    "type": "phrase",
                    "query": "term3",
                    "slop": 0
                  }
                }
              },
              {
                "match": {
                  "description": {
                    "type": "phrase",
                    "query": "term3",
                    "slop": 0
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

You can tweak this code such that all the match commands are under the same should node. I'll leave that up to you to figure out :)

like image 101
bittusarkar Avatar answered Oct 21 '22 22:10

bittusarkar