Multiple should queries with must query - elasticsearch

I am building a query to Elastic 5 (using nest in .net), i am trying to achive this result:
Must have value1 and value 2
Should have value3 or value 4
and should have value5 or value6
Here is my query:
{
"query": {
"bool": {
"must": [
{
"match": {
"code": {
"query": "value1"
}
}
},
{
"match": {
"code": {
"query": "value2"
}
}
}
],
"should": [
{
"match": {
"code": {
"query": "value3"
}
}
},
{
"match": {
"code": {
"query": "value4"
}
}
}
],
"should": [
{
"match": {
"code": {
"query": "value5"
}
}
},
{
"match": {
"code": {
"query": "value6"
}
}
}
],
"minimum_should_match": 1
}
}
}
I dont get the desired answer (for example i dont have anywhere value 5 and value 6 but still getting results)
Thank you

Then you need something like this:
{
"query": {
"bool": {
"must": [
{
"match": {
"code": {
"query": "value1"
}
}
},
{
"match": {
"code": {
"query": "value2"
}
}
},
{
"bool": {
"minimum_should_match": 1,
"should": [
{
"match": {
"code": {
"query": "value3"
}
}
},
{
"match": {
"code": {
"query": "value4"
}
}
}
]
}
},
{
"bool": {
"minimum_should_match": 1,
"should": [
{
"match": {
"code": {
"query": "value5"
}
}
},
{
"match": {
"code": {
"query": "value6"
}
}
}
]
}
}
]
}
}
}

Here's the NEST equivalent of Val's answer
void Main()
{
var client = new ElasticClient();
client.Search<MyDocument>(s => s
.Query(q => q
.Bool(b => b
.Must(mu => mu
.Match(m => m
.Field(f => f.Code)
.Query("value1")
), mu => mu
.Match(m => m
.Field(f => f.Code)
.Query("value2")
), mu => mu
.Bool(bb => bb
.MinimumShouldMatch(1)
.Should(sh => sh
.Match(m => m
.Field(f => f.Code)
.Query("value3")
), sh => sh
.Match(m => m
.Field(f => f.Code)
.Query("value4")
)
)
), mu => mu
.Bool(bb => bb
.MinimumShouldMatch(1)
.Should(sh => sh
.Match(m => m
.Field(f => f.Code)
.Query("value5")
), sh => sh
.Match(m => m
.Field(f => f.Code)
.Query("value6")
)
)
)
)
)
)
);
}
public class MyDocument
{
public string Code { get; set; }
}

Here's another example using more concrete sample data:
{
"query": {
"bool": {
"must": [
{
"match": {
"author": {
"query": "Anita Author"
}
}
},
{
"match": {
"author": {
"query": "Bertha Booster"
}
}
}
],
"should":
[
{
"match": {
"title": {
"query": "A Fantastic Book"
}
}
},
{
"match": {
"title": {
"query": "A Fantastic Book, Volume 2"
}
}
},
{
"match": {
"title": {
"query": "Yet Another Fantastic Book"
}
}
}
],
"minimum_should_match": 1
}
}
}

Related

Complex nested Elastic NEST Query

How would I convert this to the equivalent Nest query?
{
"query": {
"nested": {
"path": "boundedContexts",
"query": {
"nested": {
"path": "boundedContexts.aggregateRoots",
"query": {
"nested": {
"path": "boundedContexts.aggregateRoots.modelMetaData",
"query": {
"bool": {
"must": [
{ "match": { "boundedContexts.aggregateRoots.modelMetaData.modelReferenceId": "4e7c5c0e-93a7-4bf6-9705-cf1327760e21" } },
{ "match": { "boundedContexts.aggregateRoots.modelMetaData.modelType.name": "AggregateRoot" } }
]
}
}
}
}
}
}
}
},
"size": 1,
"sort": [
{
"generatedDate": {
"order": "desc"
}
}
]
}
I am trying to generate an equivalent Nest query similar to this ..
ISearchResponse<ViewModels.DomainModel> response = null;
response = await _elasticClient.SearchAsync<ViewModels.DomainModel>(s => s.Index(_modelMetadataProvider.CurrentIndexName)
.Query(q => q.Nested(n1 => n1.Path("boundedContexts")
.Query(q2 => q2.Nested(n2 => n2.Path("boundedContexts.aggregateRoots")
.Query(q3 => q3.Nested(n3 => n3.Path("boundedContexts.aggregateRoots.modelMetaData").
Query(q4 =>q4.Bool(b => b.Must(bs =>
bs.Match(p => p.Field("boundedContexts.aggregateRoots.modelMetaData.modelReferenceId")))))))))))
.Size(1).Sort(s=>s.Descending("generatedDate")));
I am stuck on how to compare to a variable against at the p.Field("boundedContexts.aggregateRoots.modelMetaData.modelReferenceId") level of query.

Filter Creation

So after much experimentation I have found that the syntax I needed to filter products based on their specs is as follows
{
"post_filter": {
"bool": {
"filter": [{
"nested": {
"path": "productSpecification",
"query": {
"bool": {
"filter": [{
"term": {
"productSpecification.name": "Brand"
}
},
{
"terms": {
"productSpecification.value": [
"Brand1"
]
}
}
]
}
}
}
},
{
"nested": {
"path": "productSpecification",
"query": {
"bool": {
"filter": [{
"term": {
"productSpecification.name": "Guarantee"
}
},
{
"terms": {
"productSpecification.value": [
"3 years"
]
}
}
]
}
}
}
}
]
}
}
}
I'm now experimenting with creating a QueryContainer function to build this based on the selected name/values, If anyone can give me a point in the right direction on this that would be much appreciated.
Thanks
Ok I am now building a query from nest that works but is a bit messier than the DSL query above (not sure if someone can spot what Im doing below that makes it have some unnecessary bool filters
Here is my nest syntax
.PostFilter(pf => FilterTest(elasticParams,pf))
private static QueryContainer FilterTest(ElasticParams elasticParams, QueryContainerDescriptor<Product>q) => q
.Bool(b => b
.Filter(fi => FilterSelected(elasticParams, fi)));
private static QueryContainer FilterSelected2(ElasticParams elasticParams, QueryContainerDescriptor<Product> q) =>
elasticParams.Filters.Aggregate(new QueryContainer(), (c, s) => c && +q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(qq => qq
.Bool(bo => bo
.Filter(fi => fi
.Term(tt => tt
.Field(a => a.ProductSpecification.Suffix("name"))
.Value(s.name)) && fi
.Terms(ttt => ttt
.Field(fff => fff.ProductSpecification.Suffix("value"))
.Terms(s.Values))
)))));
As mentioned before this is filters as I would want however it DSL that it outputs is rather fugly as can be seen below
{
"post_filter": {
"bool": {
"filter": [{
"bool": {
"filter": [{
"nested": {
"query": {
"bool": {
"filter": [{
"bool": {
"must": [{
"term": {
"productSpecification.name": {
"value": "Brand"
}
}
}, {
"terms": {
"productSpecification.value": ["Brand1", "Brand2"]
}
}]
}
}]
}
},
"path": "productSpecification"
}
}, {
"nested": {
"query": {
"bool": {
"filter": [{
"bool": {
"must": [{
"term": {
"productSpecification.name": {
"value": "Guarantee"
}
}
}, {
"terms": {
"productSpecification.value": ["3 years"]
}
}]
}
}]
}
},
"path": "productSpecification"
}
}]
}
}]
}
}
}

how to write existsQuery in nest 1.7

I'm using nest 1.7 and i need to write this query:
GET _search
{
"from": 0,
"size": 3,
"query": {
"bool": {
"must": [
{
"constant_score": {
"filter": {
"bool": {
"must": [
{
"bool": {
"must": [
{
"exists": {
"field": "Collaborateurs"
}
},
{
"exists": {
"field": "Collaborateurs.Nom"
}
},
{
"exists": {
"field": "Collaborateurs.Fonction"
}
},
{
"exists": {
"field": "Collaborateurs.TagVisuel"
}
},
{
"exists": {
"field": "siAnnuaire"
}
},
{
"term": {
"siAnnuaire": {
"value": true
}
}
},
{
"exists": {
"field": "TagPhoto"
}
},
{
"range": {
"NbAnnonce": {
"gt": 0
}
}
},
{
"geo_distance": {
"distance": "10.0km",
"AgenceLocation": {
"lat": 48.8523513700019,
"lon": 2.35127712591128
}
}
}
]
}
}
]
}
}
}
},
{
"function_score": {
"functions": [
{
"random_score": {
"seed": 69937385
}
}
]
}
}
]
}
}
}
Using the fluent API, the method calls follow pretty much the structure of the query DSL json
var response = client.Search<Document>(x => x
.From(0)
.Size(3)
.Query(q => q
.Bool(b => b
.Must(m => m
.ConstantScore(cs => cs
.Filter(csf => csf
.Bool(cb => cb
.Must(
cm => cm.Exists(p => p.Collaborateurs),
cm => cm.Exists(p => p.Collaborateurs.Nom),
cm => cm.Exists(p => p.Collaborateurs.Fonction)
// etc, etc for the other queries
)
)
)
), m => m
.FunctionScore(fs => fs
.Functions(fu => fu
.RandomScore(69937385)
)
)
)
)
)
);
which yields
{
"from": 0,
"size": 3,
"query": {
"bool": {
"must": [
{
"constant_score": {
"filter": {
"bool": {
"must": [
{
"exists": {
"field": "collaborateurs"
}
},
{
"exists": {
"field": "collaborateurs.nom"
}
},
{
"exists": {
"field": "collaborateurs.fonction"
}
}
]
}
}
}
},
{
"function_score": {
"functions": [
{
"random_score": {
"seed": 69937385
}
}
]
}
}
]
}
}
}
field names have been camel-cased by default, but you can change this behaviour using .SetDefaultPropertyNameInferrer(Func<string, string>) on ConnectionSettings.

Nest query to write must and should in bool

I want to write query that will produce result like below. Bool query takes only a boolQueryDescriptior. I want to use two boolQueryDescriptors instead.
{
"query": {
"bool": {
"must": [
{
"term": {
"id": "idValue"
}
}
],
"should": [
{
"nested": {
"path": "nestedType",
"query": {
"match": {
"item2": "item2Value"
}
},
"inner_hits": {}
}
}
]
}
}
}
var results = client.Search<object>(sd => sd
.Query(q => q
.Bool(b => b
.Must(m => m
.Term("id", "idValue"))
.Should(s => s
.Nested(nq => nq
.Path("nestedType")
.Query(qd => qd
.Match(m => m
.OnField("item2")
.Query("item2Value")))
.InnerHits()))));

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.
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 :)

Resources