I am working on a spring boot criteria API to construct the mongodb query but experiencing below error
"Due to limitations of the org.bson.document, you can't add a second '$and' expression specified"
Query query = new Query();
List<Criteria> criteriaList = new ArrayList<>();
for (ItemDto itemDto : itemDtoList) {
criteriaList.add(Criteria.where("item_id").is(itemDto.getClientId()));
criteriaList.add(Criteria.where("store_id").is(itemDto.getItem().getStoreId()));
criteriaList.add(Criteria.where("code").is(itemDto.getItem().getCode()));
}
query.addCriteria(new Criteria().orOperator(criteriaList));
List<Price> priceList = mongoOperations.find(query, Price.class);
Expected:
db.price.find( {
"$or" :
[
{"$and" :[{ "item_id" : "1234"}, { "store_id" : "1447"}, { "code" : "XYZ"}]},
{"$and" :[{ "item_id" : "1234"}, { "store_id" : "1951"}, { "code" : "XYZ"}]},
{"$and" :[{ "item_id" : "1234"}, { "store_id" : "0536"}, { "code" : "XYZ"}]},
{"$and" : [{ "item_id" : "1234"}, { "store_id" : "1751"}, { "code" : "XYZ"}]}
]
});
I tried as per Muhammed's answer and below is what I implemented which worked.
Query query = new Query();
List<Criteria> criteriaList = new ArrayList<>();
Criteria orCriteria = new Criteria();
for (ItemDto itemDto : itemDtoList) {
Criteria criteria1 = new Criteria().andOperator(Criteria.where("item_id").is(itemDto.getItemId()),
Criteria.where("store_id").is(itemDto.getStoreId()),
Criteria.where("code").is(itemDto.getCode())
);
criteriaList.add(criteria1);
}
query.addCriteria(orCriteria.orOperator(criteriaList.toArray(new Criteria[criteriaList.size()])));
List<Price> priceList = mongoOperations.find(query, Price.class);
Try like this ;
Query query = new Query(
Criteria.where...
.andOperator(
Criteria.where....,
Criteria.where...,
.
.
)
);
Related
I have a query and want to do a search by multiple fields in case if no result by uuid need to do the same by parentUuid:
C# query:
return new NestedQuery
{
Path = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Query = new BoolQuery
{
Filter = new List<QueryContainer>
{
new TermsQuery
{
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Terms = new List<string>
{
"term2"
},
Boost = 10
}
},
Should = new List<QueryContainer>
{
new TermQuery
{
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields.First().UUID),
Value = filter.UUID
},
new TermQuery
{
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields.First().ParentUUID),
Value = filter.UUID
},
}
}
};
example of documents:
"keywordFields": [
{
"value": "term1",
"uuid": "bf18ee9f-7592-488d-7985-2b9fe8b878ca",
"parentUUID": null
},
{
"value": "term2",
"uuid": "079205ed-30df-08f6-02a1-9caf093c3be0",
"parentUUID": "103d6061-cb99-4fba-8118-2ea501e4425d"
}
]
How to update the query to use or condition in case if no result by 'UUID' then let's do it by ParentUUID?
Try to combine bool query:
nested1 = new NestedQuery {
Path = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Query = new BoolQuery {
Filter = new List<QueryContainer>
{
new TermsQuery {
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Terms = new List<string>
{
"term2"
},
Boost = 10
}
}
}
};
nested2 = new NestedQuery {
Path = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Query = new BoolQuery {
Filter = new List<QueryContainer>
{
new TermsQuery {
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields),
Terms = new List<string>
{
"term2"
},
Boost = 10
}
},
Should = new List<QueryContainer>
{
new BoolQuery {
MustNot = new List<QueryContainer> {
new TermQuery
{
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields.First().UUID),
Value = filter.UUID
}
},
Must = new List<QueryContainer> {
new TermQuery
{
Field = Infer.Field<ElasticDocument>(t => t.KeywordFields.First().parentUUID),
Value = filter.UUID
}
}
}
}
}
};
And should nested1 with nested2
note: not IDE for this edit, the nested2 query could be simplify for sure.
Hi Currently I am having an elastic search index and i am trying to perform a date range query on the documents within that index using java high level rest client library . However it doesn't work as expected. so the following is my index .I have to perform a range query on the field #timestamp.
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 229081,
"max_score" : 1.0,
"hits" : [
{
"_index" : "mep-reports-2019.09.11",
"_type" : "doc",
"_id" : "68e8e03f-baf8-4bfc-a920-58e26edf835c-353899837500",
"_score" : 1.0,
"_source" : {
"account_id" : "270d13e6-2f4f-4d51-99d5-92ffba5f0cb6",
"inventory" : "SMS",
"flight_name" : "test flight 001",
"status" : "ENROUTE",
"msg_text" : "Test !!!!!!!!!!!!!!1 elastic search:is.gd/YfzotY",
"flight_id" : "68e8e03f-baf8-4bfc-a920-58e26edf835c",
"submission_ts" : "1568197286",
"recipient" : "353899837500",
"o_error" : null,
"nof_segments" : "-1",
"campaign_id" : "0fae8662-bee9-46ac-9b3e-062f4ba55966",
"campaign_name" : "Index search petri11",
"#version" : "1",
"sender" : "800111",
"delivery_ts" : "0",
"#timestamp" : "2019-09-11T10:21:26.000Z"
}
},
{
"_index" : "mep-reports-2019.09.11",
"_type" : "doc",
"_id" : "68e8e03f-baf8-4bfc-a920-58e26edf835c-353899837503",
"_score" : 1.0,
"_source" : {
"account_id" : "270d13e6-2f4f-4d51-99d5-92ffba5f0cb6",
"inventory" : "SMS",
"flight_name" : "test flight 001",
"status" : "ENROUTE",
"msg_text" : "Test !!!!!!!!!!!!!!1 elastic searchh",
"flight_id" : "68e8e03f-baf8-4bfc-a920-58e26edf835c",
"submission_ts" : "1568197286",
"recipient" : "353899837503",
"o_error" : null,
"nof_segments" : "-1",
"campaign_id" : "0fae8662-bee9-46ac-9b3e-062f4ba55966",
"campaign_name" : "Index search petri11",
"#version" : "1",
"sender" : "800111",
"delivery_ts" : "0",
"#timestamp" : "2019-09-11T10:21:26.000Z"
}
}
}
the followign is my java code that tries to do a range query on the #timestamp and it doesnt work . I am only pasting a part of the range query snippet below from my builder class named SearchRequestBuilder.
SearchRequestBuilder range(ZonedDateTime startDate, ZonedDateTime endDate) {
System.out.println(startDate); // 2019-08-31T23:00Z
System.out.println(endDate); // 2019-09-17T13:25:18.154Z
RangeQueryBuilder startRangeQueryBuilder = QueryBuilders.rangeQuery("#timestamp").gte(startDate);
startRangeQueryBuilder.format("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
boolQueryBuilder.should(startRangeQueryBuilder);
RangeQueryBuilder endRangeQueryBuilder = QueryBuilders.rangeQuery("#timestamp").lte(endDate);
boolQueryBuilder.should(endRangeQueryBuilder);
endRangeQueryBuilder.format("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
return this;
}
any idea how can i fix my range query to in order to work it correctly
the followign is a SearchRequestBuilder that builds an elastic search index
static class SearchRequestBuilder {
private SearchRequest searchRequest;
private SearchSourceBuilder searchSourceBuilder;
private BoolQueryBuilder boolQueryBuilder;
SearchRequestBuilder(String elasticSearchIndex) {
this.searchRequest = new SearchRequest(elasticSearchIndex);
this.searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());
this.searchSourceBuilder = new SearchSourceBuilder();
this.searchRequest.source(searchSourceBuilder);
this.boolQueryBuilder = QueryBuilders.boolQuery();
this.searchSourceBuilder.query(this.boolQueryBuilder);
}
SearchRequestBuilder from(Integer pageNumber) {
this.searchSourceBuilder.from(pageNumber);
return this;
}
SearchRequestBuilder size(Integer pageSize) {
this.searchSourceBuilder.size(pageSize);
return this;
}
SearchRequestBuilder timeOut(long duration, TimeUnit timeUnit) {
this.searchSourceBuilder.timeout(new TimeValue(duration, TimeUnit.SECONDS));
return this;
}
SearchRequestBuilder range(ZonedDateTime startDate, ZonedDateTime endDate) {
System.out.println(startDate);
System.out.println(endDate);
// this.boolQueryBuilder.must(QueryBuilders.rangeQuery("#timestamp").from(startDate,true).to(endDate));
//QueryBuilders.rangeQuery("FieldName").from(startDate,true).to(endDate));
RangeQueryBuilder startRangeQueryBuilder = QueryBuilders.rangeQuery("#timestamp").gte(startDate);
startRangeQueryBuilder.format("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
boolQueryBuilder.should(startRangeQueryBuilder);
RangeQueryBuilder endRangeQueryBuilder = QueryBuilders.rangeQuery("#timestamp").lte(endDate);
boolQueryBuilder.should(endRangeQueryBuilder);
endRangeQueryBuilder.format("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
return this;
}
SearchRequestBuilder msisdn(String msisdn) {
if (msisdn != null) {
if (msisdn.indexOf("*") >= 0) {
WildcardQueryBuilder msisdnWildCardQueryBuilder = new WildcardQueryBuilder("recipient", msisdn);
boolQueryBuilder.must(msisdnWildCardQueryBuilder);
} else {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("recipient", msisdn);
boolQueryBuilder.must(matchQueryBuilder);
}
}
return this;
}
SearchRequestBuilder accountId(UUID accountId) {
if (accountId != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("account_id", accountId.toString());
boolQueryBuilder.must(matchQueryBuilder);
}
return this;
}
SearchRequestBuilder campaignId(UUID campaignId) {
if (campaignId != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("campaign_id", campaignId.toString());
boolQueryBuilder.must(matchQueryBuilder);
}
return this;
}
SearchRequestBuilder flightId(UUID flightId) {
if (flightId != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("flight_id", flightId.toString());
boolQueryBuilder.must(matchQueryBuilder);
}
return this;
}
SearchRequestBuilder cdrStatus(String cdrStatus) {
if (cdrStatus != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("status", cdrStatus);
boolQueryBuilder.must(matchQueryBuilder);
}
return this;
}
SearchRequestBuilder inventoryCode(String inventoryCode) {
if (inventoryCode != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("inventory", inventoryCode);
boolQueryBuilder.must(matchQueryBuilder);
}
return this;
}
SearchRequestBuilder sort() {
searchSourceBuilder.sort(new FieldSortBuilder("#timestamp").order(SortOrder.DESC));
return this;
}
SearchRequest build() {
return this.searchRequest;
}
}
thank you so much
The main problem is that the should() clauses are not taken into account because the other clauses are must() (See why here). If you use filter() clauses instead of must() then it will work.
Example with the flightId method follows:
SearchRequestBuilder flightId(UUID flightId) {
if (flightId != null) {
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("flight_id", flightId.toString());
boolQueryBuilder.filter(matchQueryBuilder);
} ^
return this; |
} |
change this
I try to build an elasticsearch request with a must and a should request
my target request is this bellow:
POST /index/type/_search
{
"query": {
"bool" : {
"must" : {
"match":{"field1":{"query":"word1"}}
},
"should":{
"match":{"field2":{"query":"word2"}}
}
}
}}
I wrote:
var queryContainer = new QueryContainer();
queryContainer &= new MatchQuery() { Field = "field1", Query = "word1" };
queryContainer |= new MatchQuery() { Field = "field2", Query = "word2"};
var searchRequest = new SearchRequest<type>
{
Query = queryContainer
};
but it produces me :
POST /index/type/_search
{
"query":{
"bool":{
"should":[
{"match":{"field1":{"query":"word1"}}},
{"match":{"field2":{"query":"word2"}}}]
}
}
}
Do you know what should I write when building my QueryContainer?
Using operator overloading here is not that helpful; I think in this case, constructing the bool query is clearer
Object Initializer Syntax
var searchRequest = new SearchRequest<Document>()
{
Query = new BoolQuery
{
Must = new QueryContainer[] { new MatchQuery() { Field = "field1", Query = "word1" } },
Should = new QueryContainer[] { new MatchQuery() { Field = "field2", Query = "word2" } }
}
};
client.Search<Document>(searchRequest);
or Fluent API
client.Search<Document>(s => s
.Query(q => q
.Bool(b => b
.Must(fi => fi
.Match(m => m
.Field("field2")
.Query("word2")
)
)
.Should(sh => sh
.Match(m => m
.Field("field1")
.Query("word1")
)
)
)
)
);
How do I write the following mongoDB query in Spring?
db.reportData.aggregate([
{ $match : { iecode : {$in:["P110017","P111111"]} } } ,
{
"$group": {
"_id": "$iecode",
"treatmentArms": { "$first": "$evaluationDTOList" }
}
},
{ "$unwind": "$treatmentArms" },
{
"$group": {
"_id": null,
"Package": {
"$sum": {
"$cond": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] },
1, 0
]
}
},
"Behavioral-bias related mechanisms":{
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanismTested1", "Behavioral-bias related mechanisms" ] }
]
},
1,
0 ]
}
}
}
}
])
My Database name : MyProjects
Collection name : reportData
Please note that I am using spring MongoTemplate.
Edit
Here is my code which I tried
#SuppressWarnings("deprecation")
public void getIEQuestions(Map<String, List<String>> paramMap){
System.out.println("get ie questions-------------------------------------------");
DBCollection projects = getMongoOperations().getCollection("reportData");
BasicDBObject andQuery = new BasicDBObject();
List<BasicDBObject> filters = new ArrayList<BasicDBObject>();
for (Map.Entry<String, List<String>> entry : paramMap.entrySet()) {
ArrayList<String> vals = new ArrayList<String>();
boolean flag = false;
for (int i = 0; i < entry.getValue().size(); i++) {
if (entry.getValue().get(i) != null && entry.getValue().get(i) != "") {
System.out.println("entry.getValue().get(i): " + entry.getValue().get(i));
vals.add(entry.getValue().get(i));
flag = true;
}
}
if (flag) {
filters.add(new BasicDBObject(entry.getKey(), new BasicDBObject("$in", vals)));
}
}
andQuery.put("$and", filters);
//adding all the filters I want to apply
DBObject match = new BasicDBObject("$match", andQuery);
DBObject groupByIECode = new BasicDBObject("$group",
new BasicDBObject("_id","$iecode"))
.append("treatmentArms",new BasicDBObject("$first","$evaluationDTOList"));
DBObject unwind = new BasicDBObject("$unwind","$treatmentArms");
DBObject finalCalculation = new BasicDBObject("$group",new BasicDBObject("_id",null))
.append(
"Package", new BasicDBObject(
"$sum", new BasicDBObject(
"$cond", new Object[]{
new BasicDBObject(
"$eq", new Object[]{ "$treatmentArms.mechanismOrPkg", "Package"}
),
1,
0
}
)
)
);
final AggregationOutput output = projects.aggregate(match, groupByIECode, unwind, finalCalculation);
for (DBObject result1 : output.results()) {
String totalPackage = (String) result1.get("Package");
System.out.println("Total package: "+totalPackage);
}
}
It is giving me duplicateMongoKey exception.
Later I find out $cond operation is not yet supported by spring mongotemplate from here
I have 2 Types in one index and both have Suggest field (as required )
public class LegalAreaSearchModel : LegalAreaModel
{
public SuggestField Suggest
{
get
{
List<string> input = new List<string>();
string[] childArea = !string.IsNullOrEmpty(this.LegalArea) ? this.LegalArea.Split(' ') : new string[] { "" };
string[] ParentArea = !string.IsNullOrEmpty(this.Parent) ? this.Parent.Split(' ') : new string[] { "" };
input.AddRange(childArea);
input.AddRange(ParentArea);
return
new SuggestField
{
Input = input,
Output = this.LegalArea,
Payload = new
{
Id = this.RowId,
Name = !string.IsNullOrEmpty(this.Parent) ? this.Parent + "/" + this.LegalArea : this.LegalArea,
Type = "Specialization"
},
Weight = !string.IsNullOrEmpty(this.LegalArea) ? this.LegalArea.Length : 0
};
}
}
}
AND
public class LegalDocumentSearchModel : LegalDocumentModel
{
public LegalDocumentSearchModel()
{
//this = ObjectCopier.Clone<LegalDocumentSearchModel>(legalDocumentSearchModel);
SupplierDetails = new List<LegalDocumentSupplierDetailForSearch>();
CategoryDetails = new List<CategoryDetails>();
}
[Nested()]
public List<LegalDocumentSupplierDetailForSearch> SupplierDetails { get; set; }
[Nested()]
public List<CategoryDetails> CategoryDetails { get; set; }
public SuggestField Suggest
{
get
{
return
new SuggestField
{
Input = new List<string>(!string.IsNullOrEmpty(this.Name) ? this.Name.Split(' ') : new string[] { "" }) { this.Name },
Output = this.Name,
Payload = new
{
Id = this.RowId,
SEOFriendlyURLName = !string.IsNullOrEmpty(this.SEOFriendlyURLName) ? string.Concat(this.SEOFriendlyURLName) : string.Empty,
Name = this.Name,
ProductType = this.ProductType,
Description = this.Description
},
Weight = !string.IsNullOrEmpty(this.Description) ? this.Description.Length : 0
};
}
}
}
public class LegalDocumentSupplierDetailForSearch
{
public string PinCode { get; set; }
public string Lattitude { get; set; }
public string Longitude { get; set; }
}
public class CategoryDetails
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
now my search is as follows
List<AdvocateListingSuggestionModel> lsResult = new List<AdvocateListingSuggestionModel>();
var result = _searchProvider.Client.Suggest<LegalAreaSearchModel>(s => s
.Index(SearchConfigurationManager.DefaultSearchIndex)
.Completion("ml-la-suggestions", c => c
.Text(Search)
.Field(p => p.Suggest)
.Fuzzy(fz => fz
.Fuzziness(Fuzziness.Auto))
)
);
I am getting result from other type as well(mix) of both how can we restrict result from only one type
Type1.LegalAreaSearchModel
Type 2.LegalDocumentSearchModel
my index creation is as follows
public static bool CheckForIndex(ElasticSearchProvider searchProvider)
{
Nest.IndexExistsRequest idr = new Nest.IndexExistsRequest(Nest.Indices.Index(new Nest.IndexName() { Name = SearchConfigurationManager.DefaultSearchIndex }));
var indxres = searchProvider.Client.IndexExists(idr);
if (!indxres.Exists)
{
searchProvider.Client.CreateIndex(SearchConfigurationManager.DefaultSearchIndex, i => i
.Settings(s => s
.NumberOfShards(2)
.NumberOfReplicas(0)
.Analysis(analysis => analysis
.Tokenizers(tokenizers => tokenizers
.Pattern("ml-id-tokenizer", p => p.Pattern(#"\W+"))
)
.TokenFilters(tokenfilters => tokenfilters
.WordDelimiter("ml-id-words", wd => wd
.SplitOnCaseChange()
.PreserveOriginal()
.SplitOnNumerics()
.GenerateNumberParts(false)
.GenerateWordParts()
)
)
.Analyzers(analyzers => analyzers
.Custom("ml-id-analyzer", c => c
.Tokenizer("ml-id-tokenizer")
.Filters("ml-id-words", "lowercase")
)
.Custom("ml-id-keyword", c => c
.Tokenizer("keyword")
.Filters("lowercase")
)
)
)
));
}
return true;
}
Creatining Document Type as follows
public bool CreateDocumentIndex()
{
bool retVal = false;
if (Common.CheckForIndex(_searchProvider))
{
var res = _searchProvider.Client.Map<LegalDocumentSearchModel>(m =>
m.Index(SearchConfigurationManager.DefaultSearchIndex)
.Type(this.type)
.AutoMap()
.Properties(ps => ps
.String(s => s
.Name(p => p.Id)
.Analyzer("ml-id-analyzer")
.Fields(f => f
.String(p => p.Name("keyword").Analyzer("ml-id-keyword"))
.String(p => p.Name("raw").Index(FieldIndexOption.NotAnalyzed))
)
)
.Completion(c => c
.Name(p => p.Suggest)
.Payloads()
)
));
retVal = res.IsValid;
}
return retVal;
}
creating LegalArea Type as follows
public bool CreateLegalAreaIndex()
{
bool retVal = false;
if (Common.CheckForIndex(_searchProvider))
{
var res = _searchProvider.Client.Map<LegalAreaSearchModel>(m =>
m.Index(SearchConfigurationManager.DefaultSearchIndex)
.Type(this.type)
.AutoMap()
.Properties(ps => ps
.String(s => s
.Name(p => p.Id)
.Analyzer("ml-id-analyzer")
.Fields(f => f
.String(p => p.Name("keyword").Analyzer("ml-id-keyword"))
.String(p => p.Name("raw").Index(FieldIndexOption.NotAnalyzed))
)
)
.Completion(c => c
.Name(p => p.Suggest)
.Payloads()
)
));
retVal = res.IsValid;
}
return retVal;
}
now when i am running legal area suggestions as follows
public List<AdvocateListingSuggestionModel> LegalAreaSuggestion(string Search)
{
List<AdvocateListingSuggestionModel> lsResult = new List<AdvocateListingSuggestionModel>();
var result = _searchProvider.Client.Suggest<LegalAreaSearchModel>(s => s
.Index(SearchConfigurationManager.DefaultSearchIndex)
.Completion("ml-la-suggestions", c => c
.Text(Search)
.Field(p => p.Suggest)
.Fuzzy(fz => fz
.Fuzziness(Fuzziness.Auto))
)
);
if (result.IsValid)
{
lsResult = result.Suggestions["ml-la-suggestions"]
.FirstOrDefault()
.Options
.Select(suggest => suggest.Payload<AdvocateListingSuggestionModel>()).ToList();
}
return lsResult;
}
I am getting results of LegalDocumentSearchModel as well.pls advise
Thanks
Suggesters work at the index level, that is, one Finite State Transducer (FST) is created for a field per index so if you have two types with the same field name in the same index (both fields must be of the same type), both types will have data represented in the one FST.
We can see this with a simple example. Here we have two types with completion fields that we're going to index into the same index
public class Band
{
public int Id { get; set;}
public CompletionField<object> Suggest { get; set;}
}
public class Sport
{
public int Id { get; set;}
public CompletionField<object> Suggest { get; set; }
}
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var defaultIndex = "default-index";
var connectionSettings = new ConnectionSettings(pool)
.DefaultIndex(defaultIndex);
var client = new ElasticClient(connectionSettings);
// just so we can re-run this example...
if (client.IndexExists(defaultIndex).Exists)
client.DeleteIndex(defaultIndex);
client.CreateIndex(defaultIndex, ci => ci
.Mappings(m => m
.Map<Band>(l => l
.AutoMap()
.Properties(p => p
.Completion(c => c
.Name(n => n.Suggest)
)
)
)
.Map<Sport>(l => l
.AutoMap()
.Properties(p => p
.Completion(c => c
.Name(n => n.Suggest)
)
)
)
)
);
var bands = new List<Band>
{
new Band { Id = 1, Suggest = new CompletionField<object> { Input = new [] {"Bowling for Soup"} } },
new Band { Id = 2, Suggest = new CompletionField<object> { Input = new [] {"Fastball"} } },
new Band { Id = 3, Suggest = new CompletionField<object> { Input = new [] {"Dropkick Murphys"} } },
new Band { Id = 4, Suggest = new CompletionField<object> { Input = new [] {"Yellowcard"} } },
new Band { Id = 5, Suggest = new CompletionField<object> { Input = new [] {"American Football"} } },
};
client.IndexMany(bands);
var sports = new List<Sport>
{
new Sport { Id = 1, Suggest = new CompletionField<object> { Input = new [] {"Bowling"} } },
new Sport { Id = 2, Suggest = new CompletionField<object> { Input = new [] {"Football"} } },
new Sport { Id = 3, Suggest = new CompletionField<object> { Input = new [] {"Baseball"} } },
new Sport { Id = 4, Suggest = new CompletionField<object> { Input = new [] {"Table Tennis"} } },
new Sport { Id = 5, Suggest = new CompletionField<object> { Input = new [] {"American Football"} } },
};
client.IndexMany(sports);
client.Refresh(defaultIndex);
var suggestResponse = client.Suggest<Band>(s => s
.Completion("suggestion", cs => cs
.Text("Bo")
.Field(f => f.Suggest)
)
);
}
We get back the following from our suggest call
{
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"suggestion" : [ {
"text" : "Bo",
"offset" : 0,
"length" : 2,
"options" : [ {
"text" : "Bowling",
"score" : 1.0
}, {
"text" : "Bowling for Soup",
"score" : 1.0
} ]
} ]
}
We get a suggestion of both a Band type and a Sport type in the results. Perhaps not what we expected or wanted, but that's how suggesters work. Note that in the .Suggest<T>() call, the type T is used to provide strongly typed access to properties that may be used in the query. In this example, the .Suggest field of type Band.
The recommended solution here is to have separate indices for different types; you can still query across multiple indices when you need to and avoid the pitfalls that can come with having multiple types in the same index.
With that aside, you can get this to work by using a Context Suggester and taking advantage of category context for the completion type mapping. Ideally, you want to use a field that exists within the document for the category to afford you flexibility in the category, but you can however use a metadata field if you want to. I recommend investigating and researching approaches for your domain.
With our previous example, if we now include category context in the mapping (same code as before but with this updated mapping)
client.CreateIndex(defaultIndex, ci => ci
.Mappings(m => m
.Map<Band>(l => l
.AutoMap()
.Properties(p => p
.Completion(c => c
.Name(n => n.Suggest)
.Context(sc => sc
.Category("type", csc => csc
// recommend that you use another field on
// the document here instead of a metadata field
.Field("_type")
)
)
)
)
)
.Map<Sport>(l => l
.AutoMap()
.Properties(p => p
.Completion(c => c
.Name(n => n.Suggest)
.Context(sc => sc
.Category("type", csc => csc
// recommend that you use another field on
// the document here instead of a metadata field
.Field("_type")
)
)
)
)
)
)
);
And use category context whilst searching
var suggestResponse = client.Suggest<Band>(s => s
.Completion("suggestion", cs => cs
.Text("Bo")
.Field(f => f.Suggest)
.Context(d => d.Add("type", "band"))
)
);
then we get the desired result
{
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"suggestion" : [ {
"text" : "Bo",
"offset" : 0,
"length" : 2,
"options" : [ {
"text" : "Bowling for Soup",
"score" : 1.0
} ]
} ]
}