OR and AND Operators in Elasticsearch query - java

I have few json document with the following format :-
_source: {
userId: "A1A1",
customerId: "C1",
component: "comp_1",
timestamp: 1408986553,
}
I want to query the document based on the following :-
(( userId == currentUserId) OR ( customerId== currentCustomerId) OR (currentRole ==ADMIN) ) AND component= currentComponent)
I tried using the SearchSourceBuilder and QueryBuilders.matchQuery, but I wasnt able to put multiple sub queries with AND and OR operators.
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("userId",userId)).sort("timestamp", SortOrder.DESC).size(count);
How we query elasticsearch using OR and AND operators?

I think in this case the Bool query is the best shot.
Something like :
{
"bool" : {
"must" : { "term" : { "component" : "comp_1" } },
"should" : [
{ "term" : { "userId" : "A1A1" } },
{ "term" : { "customerId" : "C1" } },
{ "term" : { "currentRole" : "ADMIN" } }
],
"minimum_should_match" : 1
}
}
Which gives in Java:
QueryBuilder qb = QueryBuilders
.boolQuery()
.must(termQuery("component", currentComponent))
.should(termQuery("userId", currentUserId))
.should(termQuery("customerId", currentCustomerId))
.should(termQuery("currentRole", ADMIN))
.minimumNumberShouldMatch(1)
The must parts are ANDs, the should parts are more or less ORs, except that you can specify a minimum number of shoulds to match (using minimum_should_match), this minimum being 1 by default I think (but you could set it to 0, meaning that a document matching no should condition would be returned as well).
If you want to do more complex queries involving nested ANDs and ORs, simply nest other bool queries inside must or should parts.
Also, as you're looking for exact values (ids and so on), maybe you can use term queries instead of match queries, which spare you the analysis phase (if those fields are analyzed at all, which doesn't necessarily make sense for ids). If they are analyzed, you still can do that, but only if you know exactly how your terms are stored (standard analyzer stores them lower cased for instance).

If you use a query_string query, your ANDs and ORs will be interpreted as such by the Lucene library.
This allows you to search for
(currentUserId OR currentCustomerId) AND currentComponent
for instance. By default, the values will be searched for in all fields.

Related

How to handle multiple 'and' 'or' operators in Elastic search Querybuilder in java

I have a POST body JSON which has one of the input as below
q = (a=1 and b=2 or c=3 and d=8)
I need to form a querybuilder elastic query using java
BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb1= QueryBuilders.matchQuery("a", "1");
qb2= QueryBuilders.matchQuery("b", "2");
qb3= QueryBuilders.matchQuery("c", "1");
qb4= QueryBuilders.matchQuery("d", "1");
qb.must(qb1);
qb.must(qb2);
qb.should(qb3);
qb.must(qb4);
Will elastic search take care of the and operation to be done first and then or to be considered.
How to I handle multiple and and or combinations (user is free to type anything ).
I am new to Elastic search would appreciate some help
I think bool query might be helpful to you
{
"bool" : {
"should" : [
{ "must" : { "term" : { "a" : "1" } } },
{ "must" : { "term" : { "b" : "1" } } }
]
"should" : [
{ "must" : { "term" : { "c" : "1" } } },
{ "must" : { "term" : { "d" : "1" } } }
]
}
}
sharing a pseudo code, hope it helps
elasticbuilder.boolQuery().should([
elasticbuilder.boolQuery().must([
elasticbuilder.matchQuery(1)
elasticbuilder.matchQuery(2)
])
elasticbuilder.boolQuery().must([
elasticbuilder.matchQuery(3)
elasticbuilder.matchQuery(4)
])
])
Usually I just create an example query in Kibana and then 'translate' it to the Java client api.
Not sure if this applies to you but if you want to create a full text search you could provide hints to the user as per how it is in ES docs.
The simple_query_string query supports the following operators:
+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
...

Firebase query to fetch data in given range

I have a node called quotes in Firebase. I'm facing issues while fetching data in Android for a particular range. I want to fetch 3 continues quotes id starting from 2. Here is my query database:
"quotes" : {
"-L75elQJaD3EYPsd4oWS" : {
"authorName" : "Hellen v",
"famousQuote" : "When one door of happiness closes, another opens; but often we look so long at the closed door that we do not see the one which has been opened for us.",
"id" : "1",
"uploadedBy" : "Admin"
},
"-L7GOvDNI-o_H8RvNwoN" : {
"authorName" : "Rocky Balboa",
"famousQuote" : "It's not about how hard you can hit; it's about how hard you can get hit and keep moving forward.",
"id" : "2",
"uploadedBy" : "Admin"
},
"-L7GP9oBv5NR1T6HlDd4" : {
"authorName" : "African proverb",
"famousQuote" : "If you want to go fast, go alone. If you want to go far, go together.",
"id" : "3",
"uploadedBy" : "Admin"
},
"-L7GPjM1F3_7Orcz0Q1q" : {
"authorName" : "A.P.J Abdul Kalam",
"famousQuote" : "Don’t take rest after your first victory because if you fail in second, more lips are waiting to say that your first victory was just luck.",
"id" : "4",
"uploadedBy" : "Admin"
},
Below is the rule which I'm using for quotes
"quotes": {
".indexOn": ".value"
}
How can I get quotes which has id 2,3 and 4?
If you have more than 4 records in your database, to solve this, you can use a query in which you should combine startAt() and endAt() methods to limit both ends of your query like this:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
Query query = rootRef.child("quotes").orderByChild("id").startAt("2").endAt("4");
query.addListenerForSingleValueEvent(/* ... */);
Please see here more informations about Firebase Query's startAt() method:
Create a query constrained to only return child nodes with a value greater than or equal to the given value, using the given orderBy directive or priority as default.
And here are more informations about Firebase Query's endAt() method:
Create a query constrained to only return child nodes with a value less than or equal to the given value, using the given orderBy directive or priority as default.
Edit: According to your comment, if you only want the items that have the id property set to 2, 3 and 4, you should use nested queries like this:
Query queryTwo = rootRef.child("quotes").orderByChild("id").equalsTo("2");
queryTwo.addListenerForSingleValueEvent(
List<Item> list = new ArrayList();
list.add(itemTwo);
Query queryThree = rootRef.child("quotes").orderByChild("id").equalsTo("3");
queryThree.addListenerForSingleValueEvent(
list.add(itemThree);
Query queryFour = rootRef.child("quotes").orderByChild("id").equalsTo("4");
queryFour.addListenerForSingleValueEvent(
list.add(itemFour);
//Do what you need to do with the list that contains three items
);
);
);

Spring data - Count distinct items from grouping

I have db of visits users to places, that contains place_id and user_id like this
{place_id : 1, user_id : 1}
{place_id : 1, user_id : 1}
{place_id : 1, user_id : 2}
{place_id : 2, user_id : 3}
{place_id : 2, user_id : 3}
And I want to get amount of distinct users in each place. I ended up with following native mongo aggregation:
db.collection.aggregate([{
$group: {
_id: "$place_id",
setOfUsers: {
$addToSet: "$user_id"
}
}
}, {
$project: {
distinctUserCount: {
$size: "$setOfUsers"
}
}
}])
And now I want to implement it using Spring Data, the problem now is $size operation in projection, since Spring data API does not have such, at least I haven't found it in reference.
GroupOperation group = Aggregation.group("place_id").addToSet("user_id").as("setOfUsers");
ProjectionOperation project = Aggregation.project(). .... ?
Maybe there is any way to create size field also, than the nested api can be used:
Aggregation.project().and("distinctUserCount").nested( ???);
Any help is appreciated.
I am going to answer this in "one hit", so rather than address your "$project" issue, I'm going to advise here that there is a better approach.
The $addToSet operator will create a "unique" array ( or "set" ) of the elements you ask to add to it. It is however basically another form of $group in itself, with the difference being the elements are added to an "array" ( or "set" ) in results.
This is "bad" for scalability, as your potential problem here is that the "set" actually exceeds the BSON limit for document size. Maybe it does not right now, but who knows what the code you write right now will be doing in ten years time.
Therefore, since $group is really the same thing, and you also need "two" pipeline stages to get the "distinct" count, then just to "two" $group stages instead:
Aggregation pipeline = newAggregation(
group(fields("place_id","user_id")),
group("_id.place_id").count().as("distinctUserCount")
);
Being the shell equivalent of:
[
{ "$group": {
"_id": { "place_id": "$place_id", "user_id": "$user_id" }
}},
{ "$group": {
"_id": "$_id.place_id",
"distinctUserCount": { "$sum": 1 }
}}
]
This is simple code and it is much more "scalable" as the individualt "user_id" values are at first contained in separate documents in the pipeline. Therefore the "second" $group ( in place of a $project with $size ) "counts" the distinct amounts that were already determined in the first grouping key.
Learn the limitations and pitfalls, and code well.

How to update embedded mongo document using spring mongo data api

I need some help updating property of embedded collection with JSON structure below -
translation
{
"_id" : ObjectId("533d4c73d86b8977fda970a9"),
"_class" : "com.xxx.xxx.translation.domain.Translation",
"locales" : [
{
"_id" : "en-US",
"description" : "English (United States)",
"isActive" : true
},
{
"_id" : "pt-BR",
"description" : "Portuguese (Brazil)",
"isActive" : true
},
{
"_id" : "nl-NL",
"description" : "Dutch (Netherlands)",
"isActive" : true
}
],
"screens" : [
{
"_id" : "caseCodes",
"dictionary" : [
{
"key" : "CS_CAT1",
"parameterizedValue" : "My investigations",
"locale" : "en-US"
},
{
"key" : "MY_INVESTIGATIONS",
"parameterizedValue" : "",
"locale" : "pt-BR"
},
}
]
}
In above structure:
I want to update "parameterizedValue" usinng spring-data-mongo-db API 1.3.4, for screen with _id="caseCodes" and key = "CS_CAT1".
I tried (here 'values' is an collection name for TranslationValue array)
mongoOperations.updateFirst(Query.query(Criteria.where("screens._id")
.is("caseCodes")), new Update().push(
"screens.dictionary.$.values", translationValue),
Translation.class);
but it said, "can't append array to string "dictionary"....
Any pointers or help here? Thanks.
-Sanjeev
There are a few problems with your logic as well as problems with your schema for this type of update.
Firstly what you have are nested arrays, and this causes a problem with updates as described in the documentation for the positional $ operator. What this means is that any condition matching an element of an array on the query side of the update statement will only match the first array index found.
Since you need a specific entry in the inner array you would need to match that as well. But the "catch" says that only the first match will be used in the positional operator so you cannot have both. The query form (if it were possible to work, which it does not) would actually be something like this (native shell):
db.collection.update(
{
"screens._id": "caseCodes",
"screens.dictionary.key": "CS_CAT1"
},
{
"$set": {
"screens.$.dictionary.$.parameterizedValue": "new value"
}
}
)
Now that would "appear" to be more correct than what you are doing, but of course this fails because the positional operator cannot be used more than once. I may just quite stupidly work in this case as it just so happens that the first matched index of the "screens" array (which is 0) happens to be exactly the same as the required index of the inner element. But really that is just "dumb luck".
To illustrate better, what you need to do with these type of updates is already know the indexes of the elements and place those values directly into the update statement using "dot notation". So updating your second "dictionary" element would go like this:
db.collection.update(
{
"screens._id": "caseCodes",
"screens.dictionary.key": "MY_INVESTIGATIONS"
},
{
"$set": {
"screens.0.dictionary.1.parameterizedValue": "new value"
}
}
)
Also noting that the correct operator to use here is $set as you are not appending to either of the arrays, but rather you wish to change an element.
Since that sort of exactness in updates is unlikely to suit what you need, then you should look at changing the schema to accommodate your operations in a much more supported way. So one possibility is that your "screens" data may not possibly need to be an array, and you could change that to a document form like so:
"screens" : {
"caseCodes": [
{
"key" : "CS_CAT1",
"parameterizedValue" : "My investigations",
"locale" : "en-US"
},
{
"key" : "MY_INVESTIGATIONS",
"parameterizedValue" : "",
"locale" : "pt-BR"
},
]
}
This changed the form to:
db.collection.update(
{
"screens.caseCodes.key": "CS_CAT1"
},
{
"$set": {
"screens.caseCodes.$.parameterizedValue": "new value"
}
}
)
That may or may not work for your purposes, but you either live with the limitations of using a nested array or otherwise change your schema in some way.

Parsing strings to mongodb query documents with operators in java

In a project I'm working on, at one point I read a query to mongodb from a string. I've been using com.mongodb.util.JSON.parse(querystring) to read the query, which worked fine until I started reading queries that contained operators like $max and $min. At that point, rather than using mongodb's $max operator, the parser instead creates a "$max" field. For instance,
the input string:
{ $query : { state : "AL" } , $max : { pop : 9058 } }
is parsed to the DBObject:
{ "$query" : { "state" : "AL"} , "$max" : { "pop" : 9058}}
When I then look for a DBCursor with that query document, I get a cursor of size 0 (no matching document found in the databse), presumably because there are no documents with "$query" or "$max" fields.
Is there something I can use besides JSON.parse()? I'm not averse to writing my own function for it, but how can I get a DBObject that recognizes the $ operators as operators and not fields?
Any advice would be appreciated!
The following code snippet using query modification operator $max seems to work fine.
/* {$query:{state:"AL"}, "$max":{pop:10000}}*/
String s = "{$query:{state:\"AL\"}, \"$max\":{pop:10000}}";
DBObject dbObject = (DBObject) JSON.parse(s);
System.out.println("\nFind all: ");
DBCursor cursor = collection.find(dbObject);
try {
while (cursor.hasNext()) {
DBObject cur = cursor.next();
System.out.println(cur);
}
} finally {
cursor.close();
}
Make sure you have specified index on pop.
db.zips.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "test.zips",
"name" : "id"
},
{
"v" : 1,
"key" : {
"pop" : 1
},
"ns" : "test.zips",
"name" : "pop_1"
}
]
See the following link for detail.
http://docs.mongodb.org/manual/reference/operator/max/
Just in case you are interested in using aggregation operators $max or $min, the following link provide details and sample code.
http://docs.mongodb.org/ecosystem/tutorial/use-aggregation-framework-with-java-driver/
So it turns out the DBObject as given up there worked out fine. It returns a cursor with a size of 0, true, but the DBCursor's length is actually the thing I was looking for. (Previously, I had been checking whether the cursor's size was 0, and if it was, returning null.)
I'm not quite sure what the difference between size and length is in a DBCursor (the difference between size and count is apparent, but I'm not sure what length is supposed to be), but it works now. In the case above, size and count were both 0 but length was the desired number.

Categories