using multiple criteria for grouping with the mongo 3 aggregation pipeline - java

In the example found here (http://mongodb.github.io/mongo-java-driver/3.2/builders/aggregation/), the aggregation grouping is based on "$customerId":
collection.aggregate(Arrays.asList(match(eq("author", "Dave")),
group("$customerId", sum("totalQuantity", "$quantity"),
avg("averageQuantity", "$quantity"))
out("authors")));
How would one add another grouping field into the aggregation pipepline, like say, "$systemId"?
Within the mongo-shell, you would do something like this:
db.coll.aggregate(
[
{
$group : {
_id : {"customerId":"$customerId", "systemId":"$systemId"},
//etc
}
}
]
)
In Java, you could build a query:
DBObject groupFields = new BasicDBObject();
DBObject groupIdFields = new BasicDBObject();
groupIdFields.put("customerId", "$customerID");
groupIdFields.put("systemId", "$systemId");
groupFields.put( "_id", groupIdFields );
//...
//... here some more aggregate criteria
//...
final AggregationOutput output = coll.aggregate(group);
Now, this seems like an awful lot of code just to get more than one grouping criterion. How is this possible with the aggregation pipeline in the mongo 3 java driver?

Related

How to print MongoDB aggregation queries in Java using spring-data-mongodb

I need to print the MongoDB aggregation query used in Java code (for debugging an empty response). I don't see a way to print the GroupOperation object, org.springframework.data.mongodb.core.aggregation.GroupOperation
I see this method:
public Document toDocument(AggregationOperationContext context), but I don't understand how to pass a value for context.
I need a way to print this object.
Here are my code snippets:
Query in Java code:
GroupOperation groupOperation = new GroupOperation(
Fields.fields("dbHost"))
.first("dbUser").as("dbUser")
.first("dbPassword").as("dbPassword");
System.out.println("groupOperation = " + groupOperation);
List<AggregationOperation> aggregationOperations = Lists.newArrayList(groupOperation);
Aggregation agg = Aggregation.newAggregation(aggregationOperations)
.withOptions(Aggregation.newAggregationOptions().allowDiskUse(true).build());
AggregationResults<Document> aggInfo = mongoTemplate.aggregate(agg, "schedulerInfo", Document.class);
List<Document> docs = aggInfo.getMappedResults();
System.out.println("docs = " + docs);
Output:
groupOperation = org.springframework.data.mongodb.core.aggregation.GroupOperation#1188e820
docs = []
Corresponding query in Mongo shell:
db.schedulerInfo.aggregate([{"$group": {"_id": "$dbHost", "dbUser": { "$first": "$dbUser" },"dbPassword": { "$first": "$dbPassword" }}}])

count the number of record by $match before $group java mongoDB

I have used aggregation for fetching records from MongoDB. I need to count the number of records return by $match before I do the groupBy (MongoDB) in java. Any help will be appreciated.
List countries =col2.distinct("country");
for(int i=0;i<countries.size();i++){
String country1= (String) countries.get(i);
List<DBObject> pipeline = new ArrayList<DBObject>(Arrays.asList(
new BasicDBObject("$match",new BasicDBObject("country", country1)),
new BasicDBObject("$group",
new BasicDBObject("_id","$job_id" )
.append("count", new BasicDBObject("$sum",1)
))));
AggregationOutput output = col2.aggregate(pipeline);}
This is a sample data:
You need to autowire
#Autowired
MongoTemplate mongoTemplate;
Then the converted mongo script in java
public List<Object> test(){
Aggregation aggregation = Aggregation.newAggregation(
match(Criteria.where("country").is("us")),
group(null)
.push("$$ROOT").as("data")
.count().as("count")
).withOptions(AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build());
return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(YOUR_COLLECTION.class), Object.class).getMappedResults();
}
I haven't tested it, but hope, this should work.
Working Mongo playground

Spring Data MongoDB - sort by multiple fields does not work

I have a MongoRepository with the following method:
Position findFirstByDeviceIdAndSensorUsedIsIn(String deviceId, String[] sensorsUsed, Sort sort);
And i call it like this:
return posRepo.findFirstByDeviceIdAndSensorUsedIsIn(deviceId, VALID_SENSORS, new Sort(new Sort.Order(Sort.Direction.DESC, "Time"), new Sort.Order(Sort.Direction.DESC, "TimeReceived")));
VALID_SENSORS is a String-Array with 2 entries.
The problem now is, that it sorts by Time but the second dimension (TimeReceived) is random.
The TRACE-Output of the mongodb driver is:
com.mongodb.TRACE : find: company.position { "$query" : { "device_id" : "testId" , "sensor_used" : { "$in" : [ "CELL_LOCATE" , "GPS"]}} , "$orderby" : { "time" : -1 , "time_rcvd" : -1}}
When i try the following query with my mongoclient robomongo the order is correct. Here is the query:
db.getCollection('position').find({device_id:'testdevice'}).sort({time:-1,time_‌​rcvd:-1}).limit(5)
What can cause this strange behavior?
EDIT:
I also tried the following code in my spring application:
TypedAggregation<Position> agg = newAggregation(Position.class,
match(where("deviceId").is(deviceId).andOperator(where("sensorUsed").in(VALID_SENSORS))),
sort(Sort.Direction.DESC, "time"),
sort(Sort.Direction.DESC, "timeReceived"),
limit(1)
);
AggregationResults<Position> result = template.aggregate(agg, Position.class);
But it does not work either! :(
You'll need to use the Aggregation framework, which was introduced in Mongo 2.2.
$unwind is the important operation, which duplicates each document in the aggregation pipeline, doing so once per array element. You'll want to unwind both time and time_rcvd, and then $sort them.
With the aggregation framework, you can do the following:
TypedAggregation<Position> agg = newAggregation(Position.class,
match(
where("deviceId").is(deviceId).andOperator(
where("sensorUsed").in(VALID_SENSORS)
)
),
sort(Sort.Direction.DESC, "time").and(Sort.Direction.DESC, "timeReceived"),
limit(1)
);
AggregationResults<Position> result = template.aggregate(agg, Position.class);

MongoDB-Java: How to make $geoNear to first do query, then distance?

I'm trying to query and sort documents as followed:
Query only for documents older than SOMETIME.
Within range of AROUNDME_RANGE_RADIUS_IN_RADIANS.
Get distance for each document.
Sort them by time. New to Old.
Overall it should return up to 20 results.
But it seems that since $geoNear is by default limited to 100 results, I get unexpected results.
I see $geoNear working in the following order:
Gets docs from the entire collection, by distance.
And only then executes the given Query.
Is there a way to reverse the order?
MongoDB v2.6.5
Java Driver v2.10.1
Thank you.
Example document in my collection:
{
"timestamp" : ISODate("2014-12-27T06:52:17.949Z"),
"text" : "hello",
"loc" : [
34.76701564815013,
32.05852053407342
]
}
I'm using aggregate since from what I understood it's the only way to sort by "timestamp" and get the distance.
BasicDBObject query = new BasicDBObject("timestamp", new BasicDBObject("$lt", SOMETIME));
// aggregate: geoNear
double[] currentLoc = new double[] {
Double.parseDouble(myLon),
Double.parseDouble(myLat)
};
DBObject geoNearFields = new BasicDBObject();
geoNearFields.put("near", currentLoc);
geoNearFields.put("distanceField", "dis");
geoNearFields.put("maxDistance", AROUNDME_RANGE_RADIUS_IN_RADIANS));
geoNearFields.put("query", query);
//geoNearFields.put("num", 5000); // FIXME: a temp solution I would really like to avoid
DBObject geoNear = new BasicDBObject("$geoNear", geoNearFields);
// aggregate: sort by timestamp
DBObject sortFields = new BasicDBObject("timestamp", -1);
DBObject sort = new BasicDBObject("$sort", sortFields);
// aggregate: limit
DBObject limit = new BasicDBObject("$limit", 20);
AggregationOutput output = col.aggregate(geoNear, sort, limit);
You could add a $match stage at the top of the pipleine, to filter the documents before the $geonear stage.
BasicDBObject match = new BasicDBObject("timestamp",
new BasicDBObject("$lt", SOMETIME));
AggregationOutput output = col.aggregate(match,geoNear, sort, limit);
The below piece of code now, is not required,
geoNearFields.put("query", query);

mongodb java driver hide id field in aggregation/projection operation

I'm performing an aggregation operation using the java mongodb driver, and I followed the example from the docs (pasted below). According to this, the _id field should be hidden. However, in my experience with my own code as well as the output of this example, the _id field doesn't hide even when setting the projection value to 0 (it works from the mongo shell). Does anyone know if this is a bug in the mongodb java driver? Or am I doing something incorrectly?
// create our pipeline operations, first with the $match
DBObject match = new BasicDBObject("$match", new BasicDBObject("type", "airfare") );
// build the $projection operation
DBObject fields = new BasicDBObject("department", 1);
fields.put("amount", 1);
fields.put("_id", 0);
DBObject project = new BasicDBObject("$project", fields );
// Now the $group operation
DBObject groupFields = new BasicDBObject( "_id", "$department");
groupFields.put("average", new BasicDBObject( "$avg", "$amount"));
DBObject group = new BasicDBObject("$group", groupFields);
// run aggregation
AggregationOutput output = collection.aggregate( match, project, group );
The _id field you are getting at the end is from the $group operator. If you want to rename it back to department, add another $project to the end of the pipeline and translate _id to department.

Categories