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

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

Related

How do I search an array that's nested in an array of objects in Elastic?

I have an Elastic index that contains objects structured like this:
{
dogs: [
{
name: 'wiener dog',
id: 2,
cats: [
{
name: 'mean cat',
id: 5,
},
...
],
},
...
],
...
}
My question is: How do I search against this index for all documents that include a particular id in cats? A single match is fine.
What I have tried: I have tried many different queries, including nesting on dogs, and nesting on both dogs and cats. I have tried accessing the property directly via dogs.cats.id, and all combinations of the above. Here is an example in NEST:
query &= mst.Nested(n => n
.Path("dogs")
.Query(q => q
.Nested(n => n
.Path("dogs.cats")
.Query(q => q
.Terms(t => t
.Field("dogs.cats.id")
.Terms(catIds.ToList())
)
)
)
)
);
I have also tried with a single Nested with Field set to cats.id with no luck.
Any help here would be greatly appreciated. Changing the data structure at this point would be a much larger effort, and would be avoided if possible. Thanks!
From your information, I assume that the use of NestedQuery is ideal.
PUT bug_reports
{
"mappings": {
"properties": {
"dogs": {
"type": "nested",
"properties": {
"cats": {
"type": "nested"
}
}
}
}
}
}
POST bug_reports/_doc/1
{
"dogs": [
{
"name": "wiener dog",
"id": 1,
"cats": [
{
"name":"red cat",
"id": 4
},
{
"name":"mean cat",
"id": 5
}
]
}
]
}
POST bug_reports/_doc/2
{
"dogs": [
{
"name": "none dog",
"id": 2,
"cats": [
{
"name":"mean cat",
"id": 5
}
]
}
]
}
GET bug_reports/_search?filter_path=hits.hits
{
"query": {
"nested": {
"path": "dogs",
"query": {
"bool": {
"must": [
{
"nested": {
"path": "dogs.cats",
"query": {
"terms": {
"dogs.cats.id": [
4
]
}
}
}
},
{
"nested": {
"path": "dogs.cats",
"query": {
"terms": {
"dogs.cats.id": [
5
]
}
}
}
}
]
}
}
}
}
}

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.

Multiple should queries with must query

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
}
}
}

Convert DSL Query To NEST Query

My DSL query is below :
{
"from": 0,
"size": 200,
"query": {
"filtered": {
"filter": {
"bool": {
"must": {
"query": {
"match": {
"contactId": {
"query": "e84aca88-7b82-43d9-8788-4cc25af0c43a",
"type": "phrase"
}
}
}
}
}
}
}
}
}
I tried to convert dsl query to NEST query.
var descriptor =
new SearchDescriptor<dynamic>().From(0)
.Size(200)
.Query(
a =>
a.Filtered(
b =>
b.Filter(
c =>
c.Bool(
d =>
d.Must(
e =>
e.Query(
f =>
f.Match(
g =>
g.OnField("contactId")
.Query(
"e84aca88-7b82-43d9-8788-4cc25af0c43a"))))))));
string result = System.Text.Encoding.Default.GetString(client.Serializer.Serialize(descriptor));
I examined the json results with the help of SearchDescriptor. There were differences :
{
"from": 0,
"size": 200,
"query":
{
"filtered":
{
"filter": {
"bool":
{
"must": [
{
"fquery":
{
"query":
{
"match":
{
"contactId":
{
"query": "e84aca88-7b82-43d9-8788-4cc25af0c43a"
}
}
}
}
}]
}
}
}
}
}
As you can see fquery added but i didnt fquery my Nest query. Fquery why added? First DSL query is returning result but second DSL query is not returning any result
My updated code :
var searchResults =
client.Search<dynamic>(u=>u.From(0)
.Size(200)
.Query(
a =>
a.Filtered(
b =>
b.Filter(
c =>
c.Bool(
d =>
d.Must(
e =>
e.Query(
f =>
f.MatchPhrase(
g =>
g.OnField("contactId")
.Query(
"e84aca88-7b82-43d9-8788-4cc25af0c43a")))))))));

elasticsearch bool query combine must with OR

I am currently trying to migrate a solr-based application to elasticsearch.
I have this lucene query:
((
name:(+foo +bar)
OR info:(+foo +bar)
)) AND state:(1) AND (has_image:(0) OR has_image:(1)^100)
As far as I understand this is a combination of must clauses combined with boolean OR:
Get all documents containing (foo AND bar in name) OR (foo AND bar in info). After that filter results by condition state=1 and boost documents that have an image.
I have been trying to use a bool query with must but I am failing to get boolean OR into must clauses. Here is what I have:
GET /test/object/_search
{
"from": 0,
"size": 20,
"sort": {
"_score": "desc"
},
"query": {
"bool": {
"must": [
{
"match": {
"name": "foo"
}
},
{
"match": {
"name": "bar"
}
}
],
"must_not": [],
"should": [
{
"match": {
"has_image": {
"query": 1,
"boost": 100
}
}
}
]
}
}
}
As you can see, must conditions for info are missing.
** UPDATE **
I have updated my elasticsearch query and got rid of that function score. My base problem still exists.
OR is spelled should
AND is spelled must
NOR is spelled should_not
Example:
You want to see all the items that are (round AND (red OR blue)):
{
"query": {
"bool": {
"must": [
{
"term": {"shape": "round"}
},
{
"bool": {
"should": [
{"term": {"color": "red"}},
{"term": {"color": "blue"}}
]
}
}
]
}
}
}
You can also do more complex versions of OR, for example, if you want to match at least 3 out of 5, you can specify 5 options under "should" and set a "minimum_should" of 3.
Thanks to Glen Thompson and Sebastialonso for finding where my nesting wasn't quite right before.
Thanks also to Fatmajk for pointing out that "term" becomes a "match" in ElasticSearch Version 6.
I finally managed to create a query that does exactly what i wanted to have:
A filtered nested boolean query.
I am not sure why this is not documented. Maybe someone here can tell me?
Here is the query:
GET /test/object/_search
{
"from": 0,
"size": 20,
"sort": {
"_score": "desc"
},
"query": {
"filtered": {
"filter": {
"bool": {
"must": [
{
"term": {
"state": 1
}
}
]
}
},
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"match": {
"name": "foo"
}
},
{
"match": {
"name": "bar"
}
}
],
"should": [
{
"match": {
"has_image": {
"query": 1,
"boost": 100
}
}
}
]
}
},
{
"bool": {
"must": [
{
"match": {
"info": "foo"
}
},
{
"match": {
"info": "bar"
}
}
],
"should": [
{
"match": {
"has_image": {
"query": 1,
"boost": 100
}
}
}
]
}
}
],
"minimum_should_match": 1
}
}
}
}
}
In pseudo-SQL:
SELECT * FROM /test/object
WHERE
((name=foo AND name=bar) OR (info=foo AND info=bar))
AND state=1
Please keep in mind that it depends on your document field analysis and mappings how name=foo is internally handled. This can vary from a fuzzy to strict behavior.
"minimum_should_match": 1 says, that at least one of the should statements must be true.
This statements means that whenever there is a document in the resultset that contains has_image:1 it is boosted by factor 100. This changes result ordering.
"should": [
{
"match": {
"has_image": {
"query": 1,
"boost": 100
}
}
}
]
Have fun guys :)
This is how you can nest multiple bool queries in one outer bool query
this using Kibana,
bool indicates we are using boolean
must is for AND
should is for OR
GET my_inedx/my_type/_search
{
"query" : {
"bool": { //bool indicates we are using boolean operator
"must" : [ //must is for **AND**
{
"match" : {
"description" : "some text"
}
},
{
"match" :{
"type" : "some Type"
}
},
{
"bool" : { //here its a nested boolean query
"should" : [ //should is for **OR**
{
"match" : {
//ur query
}
},
{
"match" : {}
}
]
}
}
]
}
}
}
This is how you can nest a query in ES
There are more types in "bool" like,
Filter
must_not
I recently had to solve this problem too, and after a LOT of trial and error I came up with this (in PHP, but maps directly to the DSL):
'query' => [
'bool' => [
'should' => [
['prefix' => ['name_first' => $query]],
['prefix' => ['name_last' => $query]],
['prefix' => ['phone' => $query]],
['prefix' => ['email' => $query]],
[
'multi_match' => [
'query' => $query,
'type' => 'cross_fields',
'operator' => 'and',
'fields' => ['name_first', 'name_last']
]
]
],
'minimum_should_match' => 1,
'filter' => [
['term' => ['state' => 'active']],
['term' => ['company_id' => $companyId]]
]
]
]
Which maps to something like this in SQL:
SELECT * from <index>
WHERE (
name_first LIKE '<query>%' OR
name_last LIKE '<query>%' OR
phone LIKE '<query>%' OR
email LIKE '<query>%'
)
AND state = 'active'
AND company_id = <query>
The key in all this is the minimum_should_match setting. Without this the filter totally overrides the should.
Hope this helps someone!
If you were using Solr's default or Lucene query parser, you can pretty much always put it into a query string query:
POST test/_search
{
"query": {
"query_string": {
"query": "(( name:(+foo +bar) OR info:(+foo +bar) )) AND state:(1) AND (has_image:(0) OR has_image:(1)^100)"
}
}
}
That said, you may want to use a boolean query, like the one you already posted, or even a combination of the two.
$filterQuery = $this->queryFactory->create(QueryInterface::TYPE_BOOL, ['must' => $queries,'should'=>$queriesGeo]);
In must you need to add the query condition array which you want to work with AND and in should you need to add the query condition which you want to work with OR.
You can check this: https://github.com/Smile-SA/elasticsuite/issues/972

Resources