So I have manually built the following Query in NEST which works correctly,
What im trying to figure out is a way to build this dynamically dependent on what Product Specification Filters are passed in
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)))
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Guarantee")
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("3 years")
)))
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Brand")
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("Sony")
)))
After looking at the link that Russ has shared I think the solution is to pass in my filters
public class ElasticJsonFilter
{
public string name { get; set; }
public List<string> Values { get; set; } = new List<string>();
}
Which will give me names such as "Guarantee" and values such as "2 Years, 3 Years" looking at the code that Russ linked I can get his to run through my list of filters and spit out boolean filters but I guess I need to get this to build a nested query?
elasticParams.Filters.Aggregate(new QueryContainer(), (c, s) => c && +q.Term(p => p.Name, s), c => c);
Related
I'm trying to build a query that basically searches over all full text fields, boosting a few, but leaving all others at the default boost of 1.
When I don't include any fields, everything has a boost of 1 (we're on version 6.4.2 which supports default when no fields are specified):
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Query(request.Query)
)
)
);
However, as soon as I try to boost a single field, it removes the defaults on all the other fields, only searching on the explicit field:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
);
I tried adding a wildcard, but this still just matches on firstName (then again, the wildcard on its own doesn't match anything, so assuming I have the syntax wrong on that):
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f.Field("*.*^1"))
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
);
I also tried Booling them together, but this also just matches on firstName:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.Bool(b => b
.Should(m => m
.MultiMatch(mm => mm
.Query(request.Query)
)
)
.Should(m => m
.MultiMatch(mm => mm
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
)
)
);
I'm starting to think this isn't possible. For context, the reason I'm trying to do this is to be able to add other full text fields to the index without having to include every field in our queries, but still be able to boost certain fields.
Figured out my problem. I was chaining multiple .Fields() (plural) together, where I should only have a single .Fields() (plural) and then chain multiple .Field() (singular) together:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f
.Field("firstName^20")
.Field("*.*^1")
)
.Query(request.Query)
)
)
);
This is an extension of already answered question by Jon Skeet that you can find here.
The desired result is following:
A 100
A 80
B 80
B 50
B 40
C 70
C 30
considering you have following class:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
to get to the result (in ideal scenario) can be done with Jon Skeet's answer:
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade) })
.OrderBy(group => group.Students.FirstOrDefault().Grade);
However in my case I have to support paging in my query as well. This means performing SelectMany() and then do Skip() and Take(). But to do Skip() you have to apply OrderBy(). This is where my ordering breaks again as I need to preserve the order I get after SelectMany().
How to achieve this?
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade) })
.OrderBy(group => group.Students.FirstOrDefault().Grade).SelectMany(s => s.Students).OrderBy(something magical that doesn't break ordering).Skip(s => skip).Take(t => take);
I know I could manually sort again the records when my query is materialised but I would like to avoid this and do all of it in one SQL query that is translated from LINQ.
You can take another approach using Max instead of ordering each group and taking the first value. After that you can order by max grade, name (in case two students have the same max grade) and grade:
var query = c.Customers
.GroupBy(s => s.Name, (k, g) => g
.Select(s => new { MaxGrade = g.Max(s2 => s2.Grade), Student = s }))
.SelectMany(s => s)
.OrderBy(s => s.MaxGrade)
.ThenBy(s => s.Student.Name)
.ThenByDescending(s => s.Student.Grade)
.Select(s => s.Student)
.Skip(toSkip)
.Take(toTake)
.ToList();
All these methods are supported by EF6 so you should get your desired result.
Just re-index your list results and remove the index before returning.
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade)
})
.OrderBy(group => group.Students.FirstOrDefault().Grade)
.SelectMany(s => s.Students)
.Select((obj,index) => new {obj,index})
.OrderBy(newindex => newindex.index)
.Skip(s => skip).Take(t => take)
.Select(final=> final.obj);
I am successfully creating Aggregations with my Elastic NEST query but I would like to pass some additional information in I have seen that I can use Meta for this, but I cannot find any documentation for doing it in NEST.
HERE is my nest Aggregations code
.Aggregations(aa => aa.Terms("groups", sp => sp.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt.Field(ff => ff.ProductSpecification.Suffix("value"))))
Basically id like to be add say SpecificationId to the meta any hints?
You can add meta per aggregation, and it'll be returned on the aggregation response
var response = client.Search<User>(s => s
.Size(0)
.Query(q => +q
.Term(m => m
.Field(f => f.Badges.First().Name)
.Value("c#")
)
)
.Aggregations(a => a
.SignificantTerms("badges", st => st
.Field(f => f.Badges.First().Name)
.Meta(m => m
.Add("meta_1", "value_1")
.Add("meta_2", 2)
)
)
)
);
Ive now successfully got my c# Test win forms app working with aggregations and now I want to be able to filter on said aggregations if someone selects one (or more) of them.
Here is my query that is working exactly as I want and I am getting both price range buckets and buckets for each of the specification terms that it can find in the index.
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa.Terms("groups", sp => sp.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt.Field(ff => ff.ProductSpecification.Suffix("value"))))
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
))
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)))));
Can someone point me in the direction of how I can go about filtering down results based on selecting any of these buckets.
UPDATE (30/01/18) I have now added this to my query
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Guarantee",)
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("3 years","10 years")
)))
));
This is enabling me to pass multiple values into one Spec filter, but what im not sure on how to achieve is how to filter on multiple specs so the above filters on Guarantee with values of either 3 or 10 years
but if I also wanted to pass values of "Grey" and "Copper" to the spec of "Colour"
Adding "Colour" into My first set of terms and "Grey" and "Copper" into my second terms list breaks all filtering.
I think im close here just need a little direction
If I understand your question correctly, when the user selects a price range from the price range facets that are applicable to the query that is being executed, you'd like to apply this price range to the query?
The Post Filter serves this purpose, by applying a filter to the search results after aggregations have been calculated.
Assuming a user has selected the 50-100 price range, your query would like something like
var response = client.Search<Product>(s => s
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa
.Terms("groups", sp => sp
.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt
.Field(ff => ff.ProductSpecification.Suffix("value"))
)
)
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
)
)
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)
)
)
)
.PostFilter(pf => pf
.Range(r => r
.Field(f => f.Price)
.GreaterThanOrEquals(50)
.LessThan(100)
)
)
);
I'm trying to translate a linq query using query keywords to the method syntax.
My ultimate goal is to be able to apply additonnal restrictions at runtime each time you see a Where clause in the query. I have a total of 14 different parameters to be applied. Some are mutually exclusive, other are inclusive.
Here is the starting query :
query = from p in context.Person
where p.Firstname.ToUpper().StartsWith(firstName.ToUpper())
join v in context.Visit on p.Id equals v.PersonId
where !v.AccessionNumber.StartsWith(RISConst.PM_PREFIX)
join f in context.Finding on v.Id equals f.VisitId
join c in context.Consultation on f.Id equals c.FindingId
where c.StudyId.ToUpper().CompareTo(studyId.ToUpper()) == 0
select v;
Using Jon Skeet's excellent blog post I have been able to go that far :
query = context.Person.Where(p => p.Firstname.ToUpper().StartsWith(firstName.ToUpper()))
.Join(context.Visit, p => p.Id, v => v.Id, (p, v) => new { p, v })
.Where(z => z.v.AccessionNumber.StartsWith(RISConst.PM_PREFIX))
.Select(z => z.v)
.Join(context.Finding, v => v.Id, f => f.VisitId, (v, f) => new { v, f })
.Select(t => t.f)
.Join(context.Consultation, f => f.Id, c => c.FindingId, (f, c) => new { f, c })
.Where( u => u.c.StudyId.ToUpper().CompareTo(studyId.ToUpper()) == 0)
.Select(????);
Now I'm stuck because I need to return Visits. How can I do that ?
Any Help appreciated.
I think you lost Visists on this select statement: .Select(t => t.f). Remove it and work with whole anonymous object on next statement.
.Join(context.Consultation, x => x.f.Id, c => c.FindingId, (x, c) => new { x.v, c })
.Where( u => u.c.StudyId.ToUpper().CompareTo(studyId.ToUpper()) == 0)
.Select(u => u.v);