Spring mongoTemplate. Sort is not working in geo query (NearQuery) - mongodb

I have a problem with mongoTemplate in Spring when I am trying to query using NearQuery with a Sort. The Sort does not work:
Query query = new Query();
query.with(new Sort(Direction.DESC, "timeStamp"));
Criteria criteria = new Criteria();
criteria.and("type").is("MeasurementPoint");
query.addCriteria(criteria);
NearQuery queryN = NearQuery.near(p).maxDistance(new Distance(distance, Metrics.KILOMETERS)).num(range).query(query);
GeoResults<MeasurementPoint> geoPoints = mongoTemplate.geoNear(queryN, MeasurementPoint.class);
I do not know what I am doing wrong but the geoResult returns me the first match, not the last one (Sorted DESC). So, I assume that the Sort is not working properly.
Any idea? Is it a bug?
Thanks!

Unfortunately, isn't possible sort geoNear results since it doesn't returns a cursor and the default sort is the distance with point. What you could do is sort the results manually in Java code. Note that the code bellow ignores the distance and sorts only by "timeStamp".
List<GeoResult<Person>> results = geoPoints.getContent();
Collections.sort(results, new Comparator<GeoResult<Person>>() {
#Override
public int compare(GeoResult<Person> o1, GeoResult<Person> o2) {
return o1.getContent().getTimeStamp() == 2.getContent().getTimeStamp() ? 0 :
(o1.getContent().getTimeStamp() > o2.getContent().getTimeStamp() ? 1 : -1) ;
}
});
An alternative approach is use $geoWithin and $centerSphere. Since you're limiting results with some distance (distance variable), that could work.
Query query = Query.query(Criteria.where("coords").withinSphere(new Circle(p, new Distance(distance, Metrics.KILOMETERS).getNormalizedValue())));
query.with(new Sort(Direction.DESC, "timeStamp"));
Criteria criteria = new Criteria();
criteria.and("type").is("MeasurementPoint");
query.addCriteria(criteria);
List<Person> geoPoints = mongoTemplate.find(query, MeasurementPoint.class);
You could find more information about $geoWithin and $centerSphere here:
http://docs.mongodb.org/manual/reference/operator/geoWithin/
http://docs.mongodb.org/manual/reference/operator/centerSphere/

Related

Exclude document(s) if condition true

I have three fields in an entity:
establishmentNameEn
IsTelPublishDa
isTelSecret
I have fuzzy search on establishmentNameEn. And now i want to apply condition to exclude document(s) if field IsTelPublishDa value is 0 or isTelSecret value is 1.
My final query is: (+establishmentNameEn:kamran~1 +(-IsTelPublishDa:[0 TO 0] -isTelSecret:[1 TO 1]))
But it is not returning result.
Query code:
private org.apache.lucene.search.Query excludeDoc(QueryBuilder queryBuilder) {
List<org.apache.lucene.search.Query> queries = new ArrayList<>();
queries.add(queryBuilder.keyword().onField("IsTelPublishDa").matching(0).createQuery());
queries.add(queryBuilder.keyword().onField("isTelSecret").matching(1).createQuery());
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (Query qu : queries) {
builder.add(qu, BooleanClause.Occur.MUST_NOT);
}
return builder.build();
}
Main method:
Query fuzzyQuery = queryBuilder.keyword().fuzzy().withEditDistanceUpTo(1).onField("establishmentNameEn").matching(word).createQuery();
luceneQuery.add(fuzzyQuery);
luceneQuery.add(excludeDoc(queryBuilder));
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (Query qu : luceneQuery) {
builder.add(qu, BooleanClause.Occur.MUST);
}
This will never match anything, because the boolean query only contains negative clauses:
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (Query qu : queries) {
builder.add(qu, BooleanClause.Occur.MUST_NOT);
}
return builder.build();
That's quite confusing, but that's how Lucene works, and you're using a low-level Lucene API when you're using BooleanQuery.Builder.
Solution #1
If you want to avoid that kind of surprise in the future, make sure you always have positive clauses in your query. For example, refactor your code to add the "MUST_NOT" clause to the top-level boolean query:
// Main code
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(queryBuilder.keyword().fuzzy().withEditDistanceUpTo(1).onField("establishmentNameEn").matching(word).createQuery(), BooleanClause.Occur.MUST);
builder.add(excludedDoc(queryBuilder), BooleanClause.Occur.MUST_NOT);
private org.apache.lucene.search.Query excludedDoc(QueryBuilder queryBuilder) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(queryBuilder.keyword().onField("IsTelPublishDa").matching(0).createQuery(), BooleanClause.Occur.SHOULD);
builder.add(queryBuilder.keyword().onField("isTelSecret").matching(1).createQuery(), BooleanClause.Occur.SHOULD);
return builder.build();
}
Solution #2
Alternatively, you can just keep your code as is, but use the Hibernate Search DSL instead of BooleanQuery.Builder. The Hibernate Search DSL "fixes" some of the most confusing aspects of Lucene, so that this query will work as expected (matching all documents except those that match the clauses):
BooleanJunction<?> booleanJunction = queryBuilder.bool();
for (Query qu : queries) {
booleanJunction.mustNot(qu);
}
return booleanJunction.createQuery();
More details...
If you want to know why exactly this doesn't work...
Boolean queries will not match anything by default, unless a (positive) clause matches a document, in which case matching documents will be filtered out based on other (positive or negative) clauses.
So in your case, the query doesn't match anything, and then it's filtered out with the "must not" clauses, so it still doesn't match anything.
Just adding a MatchAllDocs clause would make it work as expected:
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
for (Query qu : queries) {
builder.add(qu, BooleanClause.Occur.MUST_NOT);
}
return builder.build();

Mongodb $set and $inc in camel

I am attempting to use the $set and $inc flags to update a field and increment a field by 1 in camel ( java ). Here is the code I am using:
from("direct:mySampleRoute").routeId("update-mongo-db-entry").setBody()
.simple("{\"person\": \"${headers.personName}\", \"job\": \"computerGuy\",\"title\": \"DR\"},"
+ "{\"$set\": {\"pay\": \"${headers.newPay}\"}, \"$inc\": {\"hrPay\": 1}}")
.toD("mongodb:mongoClient?database=" + mongoDb + "&collection="
+ mongoEmplyeeCollection + "&operation=update")
.end();
The goal of the query is to find an entry where person == ${headers.personName}, job == computerGuy, and title = DR. Once it is found, it is to set the pay field to ${headers.newPay} and inc the hrPay field by positive 1. From what I can tell, I am structuring my code exactly the same as mentioned in this post: What am I doing wrong with $set and $inc in update However, once executed, my app crashes and I am unable to see any logs wrt to why that query fails. As such, I suspect I have a fundamental problem in structuring the json update. Camel documentation located here is of limited help.
Using camel, how can I achieve my goal of making a "select" query, updating some fields using $set, and incrementing a field using $inc ?
I ended up solving my problem using a Processor to create the needed body. I slved it using this syntext within the process method:
#Override
public void process(final Exchange exchange) throws Exception {
final BasicDBObject filterQuery = new BasicDBObject();
filterQuery.append("person", exchange.getIn().getHeader("personName"));
filterQuery.append("job", "computerGuy");
filterQuery.append("title", "DR");
final BasicDBObject updateQuery = new BasicDBObject();
updateQuery.append("$set", new BasicDBObject("pay", exchange.getIn().getHeader("newPay"))
.append("location", "4th floor"));
updateQuery.append("$inc", new BasicDBObject("hrPay", 1));
final List<DBObject> query = new ArrayList<>();
query.add(filterQuery);
query.add(updateQuery);
exchange.getIn().setBody(query);
}

In mongodb version 3 using java api how do we give a hint while querying

In mongodb version 3 using java api how do we give a hint while querying
The query result is FindIterable which has MongoCursor.
How should I give a hint to use a particular index.
With older versions there is DBCursor which have API's for hint.
modifiers should be used:
BasicDBObject index = new BasicDBObject("num", 1);
BasicDBObject hint = new BasicDBObject("$hint", index);
FindIterable<Document> iterable = collection.find().modifiers(hint);
You need to use FindIterable.modifiers method to specify $hint:
private static void testHint(MongoDatabase db) {
MongoCollection<Document> col = db.getCollection("Stores");
FindIterable<Document> iterable = col.find(new Document("store","storenumbervalue"));
iterable.modifiers(new Document("$hint","index_name"));
MongoCursor curs = iterable.iterator();
while(curs.hasNext()){
System.out.println(curs.next());
}
}
index_name - actual index name in mongo.

How to use distinct with aggregate method for mongodb

I have some conditions and a level to check while fetching records from collection using MongoDB.
For this I am using below
def cursorOutput = dataSetCollection.find(whereObject,criteriaObject)
Which is working fine. but I want to use distinct with combination to above query.
def distinctMIdList = dataSetCollection.distinct(hierarchyField,whereObject)
Above is the query for distinct. How to combine two query.
I tried below which is not working
def cursorOutput = dataSetCollection.find(whereObject,criteriaObject).distinct("manager id")
whereObject is a condition to fetch results and criteriaObject is fields to fetch.
Distinct query is giving me result with only Manager id field but I am looking for other field also(use of criteriaObject).
Finally how to combine above two query. I searched for pipeline where $distinct is not available.
Thank you.
Map function:
var mapFunction = function() {
if(/*your criteria*/) {
emit("manager_id", this.manager_id);
}
};
Reduce Function:
var reduceFunction = function(managerFiled,values){
return Array.unique(values);
}

Problems with aggregation in MongoDB

Im trying to learn how to make sql like queries in Mongo and found this aggregation framework, when i run the code the average always turns out "0" all though the 'columns' have numbers in them, if i understand it right mongoDB saves all values as a strings( the number where integers at first in my sql server database which i transferred to mongoDB) so i dont have to care about types really when working with MongoDB? Can someone see why my average function dont work? The ordered quantity has number between 1.000000 - 7.000000.
DBCollection order = db.getCollection("Orderstatus");
DBObject match = new BasicDBObject("$match", new BasicDBObject("Company", "100") );
DBObject fields = new BasicDBObject("Facility", 1);
fields.put("Ordered quantity", 1);
fields.put("_id", 0);
DBObject project = new BasicDBObject("$project", fields );
DBObject groupFields = new BasicDBObject( "_id", "$Facility");
groupFields.put("average", new BasicDBObject( "$avg", "$Ordered quantity"));
DBObject group = new BasicDBObject("$group", groupFields);
AggregationOutput output = order.aggregate( match, project,group );
System.out.println(output);
The thing is that i thought that the numbers would be as integers since i got them from my sql server database where they are stored as integers using the code below. I see now that i use getString() when getting the values, is that why the numbers are strings in mongodb? how can i get them as integers?? i really want to be able to manipulate them as numbers!
StringBuilder orderstatus = new StringBuilder();
orderstatus.append("SELECT * FROM dbo.fact_orderstatus");
PreparedStatement t = connect.prepareStatement(orderstatus.toString());
DBCollection orderstat = db.getCollection("Orderstatus");
ResultSet v = t.executeQuery();
ResultSetMetaData rsm = t.getMetaData();
int column = rsm.getColumnCount();
while (v.next()) {
BasicDBObject orderObj = new BasicDBObject();
for(int x=1; x<column +1; x++){
String namn= rsm.getColumnName(x);
String custNum = (v.getString(x));
if (custNum != null && !custNum.trim().isEmpty()
&& custNum.length() != 0)
orderObj.append(namn, custNum);
}
orderstat.insert(orderObj)
The reason this is happening is that MongoDB does care about types of values you save.
If you want to store numbers, make sure they are not quoted otherwise they become stored as strings and you lose ability to manipulate them as numbers.
Javascript also differentiates between numbers and string, but MongoDB supports more number types than simple JavaScript. You can read more here.