elastic search combining multiple term and range queries is providing zero results - spring-data-elasticsearch

I am struggling on this for a week now. I need to write an elasticsearch query api in java with following combinations:
Price range (0 to n number of ranges)
Age range (0 to n number of ranges)
Search text
Term Filters (can match more than one field
I want to have OR relationship within each group (e.g. price 0-10 or 11-15) but AND relationship among different groups (e.g. price 0-10 AND productType="clothing")
I have written the below code in Java, I have verified that the data exists for the range and filter I am testing with but I am getting zero resultset. I am not sure what is missing here.
#Override
public List<Shoppingi> search(SearchCriteria criteria, Pageable pageable) {
final NativeSearchQueryBuilder searchQuery = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = boolQuery();
// apply the text search
if (StringUtils.isNotBlank(criteria.getSearchText()))
boolQuery.must(multiMatchQuery(criteria.getSearchText(), FIELD_NAMES_FOR_TERM_SEARCH));
// apply the term filters
BoolQueryBuilder termsQuery = boolQuery();
criteria.getFilters().entrySet().stream().forEach(entry -> termsQuery.should(termsQuery(entry.getKey(), entry.getValue())));
// apply the range filters
BoolQueryBuilder rangeQuery = boolQuery();
criteria.getRangeFilters().forEach((key, value) -> rangeQuery.should(rangeQuery(key).gte(value.getLowerBound()).lt(value.getUpperBound())));
boolQuery.must(termsQuery);
boolQuery.must(rangeQuery);
searchQuery.withQuery(boolQuery);
searchQuery.withPageable(pageable);
NativeSearchQuery query = searchQuery.build();
log.info("query: {}", query.getQuery().toString());
return elasticsearchTemplate.queryForList(query,Shopping.class);
}

I annotated the price object as index.no, which caused the query to not work. Once I re-indexed my data with price as non_analyzed, my code started working. So it was nothing wrong with the code above, it was the way I configured the variable I was querying against.

Related

Returning multi value in dynamic query using neo4j client

Following the question I asked: Build a dynamic query using neo4j client
I got an answer about how can I return value dynamically using string only.
When I'm trying to use the syntax to return multi values from the query it failed,
I tried the following query:
var resQuery2 = WebApiConfig.GraphClient.Cypher
.Match("(movie:Movie {title:{title}})")
.OptionalMatch("(movie)<-[r]-(person:Person)")
.WithParam("title", title)
.Return(() => Return.As<string>("movie, collect([person.name, head(split(lower(type(r)), '_')), r.roles])"));
I'm getting the following error:
The deserializer is running in single column mode, but the response
included multiple columns which indicates a projection instead. If
using the fluent Cypher interface, use the overload of Return that
takes a lambda or object instead of single string. (The overload with
a single string is for an identity, not raw query text: we can't map
the columns back out if you just supply raw query text.)
Is it possible to return multiple nodes using only strings?
We can't get an output like in the question you asked previously - this is due to the fact that you are asking for a Node (the movie) and a Collection of strings (the collect) and they have no common properties, or even styles of property.
Firstly, let's look at the painful way to do this:
var q = gc.Cypher
.Match("(movie:Movie)")
.OptionalMatch("(movie)<-[r]-(person:Person)")
.Return(() => Return.As<string>("{movie:movie, roles:collect([person.name, head(split(lower(type(r)), '_')), r.roles])}"));
var results = q.Results;
Here we take the query items (movie, r, person) and create a type with them the {} around the results, and cast that to a string.
This will give you a horrible string with the Node data around the movie and then a collection of the roles:
foreach (var m in results)
{
//This is going to be painful to navigate/use
dynamic d = JsonConvert.DeserializeObject<dynamic>(m);
Console.WriteLine(d.movie);
Console.WriteLine(d.roles);
}
You'd be a lot better off doing something like:
var q = gc.Cypher
.Match("(movie:Movie)")
.OptionalMatch("(movie)<-[r]-(person:Person)")
.Return(() => new
{
Movie = Return.As<Node<string>>("movie"),
Roles = Return.As<IEnumerable<string>>("collect([person.name, head(split(lower(type(r)), '_')), r.roles])")
});
var res = q.Results;
You could either JsonConvert.DeserializeObject<dynamic>() the Movie node, at your leisure, or write a strongly typed class.
In terms of a 'dynamic' object, I don't know how you were wanting to interact with the collect part of the return statement, if this doesn't help, you might need to update the question to show a usage expectation.

In Factual how to get results with unique field values (similar to GROUP BY in SQL)?

I've just set out on the path to discovery of Factual API and I cannot see how to achieve a retrieval of a selection of entries each with a unique value in the specified field.
For example, give me 10 results from various cities:
q.limit(10);
q.field("locality").unique(); // no such filter exists
factual.fetch("places", q);
This would be an equivalent query in MySQL:
SELECT * FROM places GROUP BY locality LIMIT 10;
What I want is a little bit similar to facets:
FacetQuery fq = new FacetQuery("locality").maxValuesPerFacet(10);
fq.field("country").isEqual("gb");
FacetResponse resp = factual.fetch("places", fq);
but instead of the total for each result I would like to see a random object with all the information.
Is anything like this possible?

GroupBy in F# and Average to values for each group

I am a brand new to F#, and I am having trouble with a simple first query. I have a data set, and I want to group the dollar amount based on the codes (which repeat in the data). Then, for each group I want the average (and eventually standard deviation) of the dollar amounts for each group. Also, I only want to look at ONE providerID, hence the 'where' clause. From my research, I have gotten this far:
let dc = new TypedDataContext()
let query2 = query { for x in dc.MyData do
groupBy x.Code into g
where (x.ProviderId = "some number of type string")
let average = query { for n in g do
averageBy n.DollarAmt }
select (g.Key, average) }
System.Console.WriteLine(query2)
With this I get a compiling error that says, "The namespace or module 'x' is not defined."
I do not understand this because when I ran the query that only collected the data with the specified providerID, it did not complain about this 'x', and I followed the same format with this 'x' for this larger query.
Any ideas? Thank you in advance.
From #kvb's comment: After the groupBy you can only access the group g, not the individual items x. Try putting the where before the groupBy.

how to get a random set of records from an index with cypher query

what's the syntax to get random records from a specific node_auto_index using cypher?
I suppose there is this example
START x=node:node_auto_index("uname:*") RETURN x SKIP somerandomNumber LIMIT 10;
Is there a better way that won't return a contiguous set?
there is no feature similar to SQL's Random() in neo4j.
you must either declare the random number in the SKIP random section before you use cypher (in case you are not querying directly from console and you use any upper language with neo4j)
- this will give a random section of nodes continuously in a row
or you must retrieve all the nodes and than make your own random in your upper language across these nodes - this will give you a random set of ndoes.
or, to make a pseudorandom function in cypher, we can try smthing like this:
START x=node:node_auto_index("uname:*")
WITH x, length(x.uname) as len
WHERE Id(x)+len % 3 = 0
RETURN x LIMIT 10
or make a sophisticated WHERE part in this query based upon the total number of uname nodes, or the ordinary ascii value of uname param, for example

Combining table, web service data in Grails

I'm trying to figure out the best approach to display combined tables based on matching logic and input search criteria.
Here is the situation:
We have a table of customers stored locally. The fields of interest are ssn, first name, last name and date of birth.
We also have a web service which provides the same information. Some of the customers from the web service are the same as the local file, some different.
SSN is not required in either.
I need to combine this data to be viewed on a Grails display.
The criteria for combination are 1) match on SSN. 2) For any remaining records, exact match on first name, last name and date of birth.
There's no need at this point for soundex or approximate logic.
It looks like what I should do is extract all the records from both inputs into a single collection, somehow making it a set on SSN. Then remove the blank ssn.
This will handle the SSN matching (once I figure out how to make that a set).
Then, I need to go back to the original two input sources (cached in a collection to prevent a re-read) and remove any records that exist in the SSN set derived previously.
Then, create another set based on first name, last name and date of birth - again if I can figure out how to make a set.
Then combine the two derived collections into a single collection. The collection should be sorted for display purposes.
Does this make sense? I think the search criteria will limit the number of record pulled in so I can do this in memory.
Essentially, I'm looking for some ideas on how the Grails code would look for achieving the above logic (assuming this is a good approach). The local customer table is a domain object, while what I'm getting from the WS is an array list of objects.
Also, I'm not entirely clear on how the maxresults, firstResult, and order used for the display would be affected. I think I need to read in all the records which match the search criteria first, do the combining, and display from the derived collection.
The traditional Java way of doing this would be to copy both the local and remote objects into TreeSet containers with a custom comparator, first for SSN, second for name/birthdate.
This might look something like:
def localCustomers = Customer.list()
def remoteCustomers = RemoteService.get()
TreeSet ssnFilter = new TreeSet(new ClosureComparator({c1, c2 -> c1.ssn <=> c2.ssn}))
ssnFilter.addAll(localCustomers)
ssnFilter.addAll(remoteCustomers)
TreeSet nameDobFilter = new TreeSet(new ClosureComparator({c1, c2 -> c1.firstName + c1.lastName + c1.dob <=> c2.firstName + c2.lastName + c2.dob}))
nameDobFilter.addAll(ssnFilter)
def filteredCustomers = nameDobFilter as List
At this point, filteredCustomers has all the records, except those that are duplicates by your two criteria.
Another approach is to filter the lists by sorting and doing a foldr operation, combining adjacent elements if they match. This way, you have an opportunity to combine the data from both sources.
For example:
def combineByNameAndDob(customers) {
customers.sort() {
c1, c2 -> (c1.firstName + c1.lastName + c1.dob) <=>
(c2.firstName + c2.lastName + c2.dob)
}.inject([]) { cs, c ->
if (cs && c.equalsByNameAndDob(cs[-1])) {
cs[-1].combine(c) //combine the attributes of both records
cs
} else {
cs << c
}
}
}

Resources