Elasticsearch nested aggregation with nested object using NEST - elasticsearch

I am trying to do an aggregation on a nested object. The following is my json. The first code sample below successfully returns the productCategory Id. However, I want to return the category id and name in the aggregation. I thought I could try the second code sample below but it doesn't work.
"productCategories": [{
"id":6,
"productId":6,
"categoryId":4,
"category":{
"parentId":2,
"name":"Air Fresheners",
"id":6
}
}]
This one aggregates the productCategory id as the key:
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Id)
)
)
)
)
But I need the category info, and this one doesn't work:
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories.First().Category)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Category.Id)
)
)
)
)

If category is simply mapped as object, then the following will work
var searchResponse = client.Search<Document>(s => s
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Category.Id)
)
)
)
)
);

Related

Async way of implementing search query in Elastic Search Nest Client .NET

I have implemented a Search Query through NEST client and was able to get the records. The code is as follows.
var response = clientProvider.Client.Search<ProjectModel>(s => s
.Index("project_index")
.Type("projects")
.Source(so => so.Excludes(f => f.Field(x => x.FileInfo.FileBase64Data)))
.Size(100)
.Query(q => q
.Bool(b => b
.Should(
m => m.QueryString(qs => qs
.Query(searchOptions.SearchTerm)
.Fields(ff => ff.Fields(fields))
.Fuzziness(Fuzziness.Auto)
),
m => m.MultiMatch(qs => qs
.Query(searchOptions.SearchTerm)
.Type(Nest.TextQueryType.PhrasePrefix)
.Fields(ff => ff.Fields(fields))
)
)
)
)
.Sort(ss => ss.Descending(SortSpecialField.Score))
);
And I am mapping the response to my Project Model as follows.
var project = response.Hits.Select(h =>
{
h.Source._id = h.Id;
h.Source.Score = h.Score;
return h.Source;
}).ToList();
When I am trying to implement the same Search in Async way that is
var response = clientProvider.Client.SearchAsync<ProjectModel>(s => s
.Index("project_index")
.Type("projects")
.Source(so => so.Excludes(f => f.Field(x => x.FileInfo.FileBase64Data)))
.Size(100)
.Query(q => q
.Bool(b => b
.Should(
m => m.QueryString(qs => qs
.Query(searchOptions.SearchTerm)
.Fields(ff => ff.Fields(fields))
.Fuzziness(Fuzziness.Auto)
),
m => m.MultiMatch(qs => qs
.Query(searchOptions.SearchTerm)
.Type(Nest.TextQueryType.PhrasePrefix)
.Fields(ff => ff.Fields(fields))
)
)
)
)
.Sort(ss => ss.Descending(SortSpecialField.Score))
);
I am not getting any errors while executing it. But I am not able to get the response.Hits objects to map it back to my original Project Model.
Thanks In advance
In SearchAsync<T>(), response is Task<ISearchResponse<T>>, so you probably want to await it
Map all documents, found in products index, onto ProductDto type:
var result = await _elasticClient.SearchAsync<ProductDto>(x => x.Index("products").MatchAll());
var documents = result.Documents;

Loading Parent and Child

Good day:
I"m trying to accomplish loading parent and children in one go. My query is this:
ISearchResponse<Models.Facilities.Facility> response = await this._elasticClient.SearchAsync<Models.Facilities.Facility>(s =>
s.Query(q => (q.GeoDistance(g => g.Boost(1.1)
.Field(f => f.BusinessLocation)
.DistanceType(GeoDistanceType.Arc)
.Distance(search.location.distance.ToString() + "m")
.Location((double)search.location.latitude, (double)search.location.longitude)
)
&&
q.HasChild<Models.Facilities.FacilityType>(c => c.Query(qq => qq.MatchAll()).Name("FacilityType")))
||
q.GeoDistance(g => g.Boost(1.1)
.Field(f => f.ServiceAreas)
.DistanceType(GeoDistanceType.Arc)
.Distance(search.location.distance.ToString() + "m")
.Location((double)search.location.latitude, (double)search.location.longitude))
)
.Aggregations(a =>
a.Children<Models.Facilities.FacilityType>("Capacity", child => child.Aggregations(ca => ca.Histogram("Capacity",
h => h.Field(p => p.Capacity)
.Interval(10)
.Missing(0))))
.Histogram("Distance", d => d.Field(f => f.BusinessLocation)
.Interval(10)
.Order(HistogramOrder.CountDescending)))
.Sort(g => g.GeoDistance(g => g.Field(f => f.BusinessLocation)
.DistanceType(GeoDistanceType.Arc)
.Order(SortOrder.Descending)
.Unit(DistanceUnit.Miles)).GeoDistance(g => g.Field(f => f.ServiceAreas)
.DistanceType(GeoDistanceType.Arc)
.Order(SortOrder.Descending))));
Facility and FacilityType extends from a parent Document. I'm trying to load the parent Facility along with its children FacilityType. Essentially if I could achieve an object like this facilityObj.facilityTypes. Let me know if this is possible.
InnerHits works for both Nested and Parent/Child relationships.

CreateIndex of elasticsearch Nest client is not creating index after changing the version from 2.3.3 to 5.4

I have an application working in Elasticsearch 2.3.3 with mapper-attachments. Now I upgraded to elasticsearch5.4. I used Nest client to Create index and Index Data.
Now indexing is not working.
My code to index is
var filters = new List<string> { "lowercase", "snowball", "nGram" };
client.CreateIndex("mydocs", c => c
.Settings(st => st
.Analysis(anl => anl
.Analyzers(h => h
.Custom("full", ff => ff
.Filters(filters)
.Tokenizer("standard"))
)
.TokenFilters(ba => ba
.Snowball("snowball", sn => sn
.Language(SnowballLanguage.English)))
.TokenFilters(bases => bases
.EdgeNGram("nGram", gram => gram
.MaxGram(20)
.MinGram(3)
.Side(EdgeNGramSide.Front)
))
)
.Setting("index.query.default_field", "file.content")
)
.Mappings(mp => mp
.Map<IndexDocument>(ms => ms
.AutoMap()
.Properties(ps => ps
.Nested<ES.SearchEngine.Models.Attachment>(n => n
.Name(sc => sc.File)
.AutoMap()
))
.Properties(at => at
.Attachment(a => a.Name(o => o.File)
.FileField(fl => fl.Analyzer("full").TermVector(TermVectorOption.WithPositionsOffsets).Store())
.TitleField(t => t.Name(x => x.Title)
.Analyzer("full")
.TermVector(TermVectorOption.WithPositionsOffsets)
)))
))
);
Error
{Type: mapper_parsing_exception Reason: "Failed to parse mapping [indexdocument]: No handler for type [attachment] declared on field [file]" CausedBy: "Type: mapper_parsing_exception Reason: "No handler for type [attachment] declared on field [file]""}
Can anyone pls help.

Elasticsearch Aggregation on objects by query on other documents

Lets say i have an index that contains documents that represent a Message in a discussion.
that document owns a discussionId property.
(it also has its own ID "that represent MessageId")
now, i need to find all discussionIds that have no documents (messages) that match a query.
for example:
"Find all discussionIds , that have no message that contains the text 'YO YO'"
how can i do that?
the class is similar to this:
public class Message
{
public string Id{get;set}
public string DiscussionId {get;set}
public string Text{get;set}
}
You just need to wrap the query that would find matches for the phrase "YO YO" in a bool query must_not clause.
With NEST
client.Search<Message>(s => s
.Query(q => q
.Bool(b => b
.MustNot(mn => mn
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
)
)
);
which, with operator overloading, can be shortened to
client.Search<Message>(s => s
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
Both produce the query
{
"query": {
"bool": {
"must_not": [
{
"match": {
"text": {
"type": "phrase",
"query": "YO YO"
}
}
}
]
}
}
}
To only return DiscussionId values, you can use source filtering
client.Search<Message>(s => s
.Source(sf => sf
.Includes(f => f
.Field(ff => ff.DiscussionId)
)
)
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
And, if you want to get them all, you can use the scroll API
var searchResponse = client.Search<Message>(s => s
.Scroll("1m")
.Source(sf => sf
.Includes(f => f
.Field(ff => ff.DiscussionId)
)
)
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
// fetch the next batch of documents, using the scroll id returned from
// the previous call. Do this in a loop until no more docs are returned.
searchResponse = client.Scroll<Message>("1m", searchResponse.ScrollId);

NEST Aggregate similar to SQL Group By

This is my class when inserting to ES
public class BasicDoc
{
public string Name { get; set; }
public string Url { get; set; }
}
I managed successfully insert my document to ES using NEST. But I'm having trouble to do a aggregation. My goals is to have something similar to SQL Group By. What I did so far:
var response = elastic.Search<BasicDoc>(s => s
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url)
))
);
I tried to aggregate my document based on BasicDoc.Url. Say I have these in my ES:
/api/call1/v1
/api/call2/v1
/api/call1/v1
When I debug, I my Nest.BucketAggregate will have 4 Items key which is api,call1, call2 and v1. I was expecting only 2 which are /api/call1/v1 and /api/call2/v1. What I'm doing wrong?
You currently have analysis set up on your Url property which means that it will be tokenized by the standard analyzer and terms stored in the inverted index. If you need to be able to search on Uri and also need to aggregate on it, then you may consider mapping it as a multi_field where one field mapping analyzes it and another does not. Here's an example index creation with mapping
client.CreateIndex("index-name", c => c
.Mappings(m => m
.Map<BasicDoc>(mm => mm
.AutoMap()
.Properties(p => p
.String(s => s
.Name(n => n.Url)
.Fields(f => f
.String(ss => ss
.Name("raw")
.NotAnalyzed()
)
)
)
)
)
)
);
When you perform your aggregation, you can now use the Uri raw field
var response = client.Search<BasicDoc>(s => s
.Size(0)
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url.Suffix("raw"))
)
)
);

Resources