Reputation: 2168
I have the following query which works fine when running from MongoDB shell:
db.feedback.aggregate([
{
$group: {
_id: {
year: { $year: '$receivedDate' },
month: { $month: '$receivedDate' }
},
count: { $sum: 1 }
}
}
])
It returns dates (year, month) and their sum.
However, when I run the same query from Spring's MongoTemplate I get the date, and the sum is always null:
private static final String QUERY_STATS_BY_MONTH = "{
aggregate: 'feedback',
pipeline: ([
{
$group:{
_id: {
year: { $year: '$receivedDate' },
month: { $month: '$receivedDate' }
},
count: { $sum: 1 }
}
}
])";
....
CommandResult commandResult = mongoTemplate.executeCommand(QUERY_STATS_BY_DAY);
BasicDBList dbList = (BasicDBList) commandResult.get(QUERY_RESULT);
Iterator<Object> it = dbList.iterator();
List<FeedbackDateAggregation> res = new ArrayList();
while (it.hasNext()) {
DBObject dbObject = (DBObject) it.next();
res.add(mongoTemplate.getConverter().read(FeedbackDateAggregation.class, dbObject));
}
return res;
My domain classes are:
public class FeedbackDateAggregation {
private StatsDate id;
private Long count;
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
public StatsDate getId() {
return id;
}
public void setId(StatsDate id) {
this.id = id;
}
}
and:
public class StatsDate {
private int year;
private int month;
private int day;
private int week;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getWeek() {
return week;
}
public void setWeek(int week) {
this.week = week;
}
}
The StatsDate are retrieved, but count is always null.
Upvotes: 1
Views: 2838
Reputation: 2168
The problem was that I used an old version of Fongo (fake mongo for tests). Once I tried it on a real MongoDB, it worked like a charm. When I updated my Fongo version, it worked as well. I also had to change my domain objects. I have now a domain object that includes day, week, month, year, count.
Upvotes: 0
Reputation: 75924
This is the idiomatic way of doing aggregation queries using spring template. You don't need to access the low level CommandResult
mongo api, only need for advance use.
Makes use of AggregationOperation
as the operator $year
and $month
are not supported out of box in the spring group stage yet.
AggregationOperation group = new AggregationOperation() {
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject(
"$group", new BasicDBObject(
"_id", new BasicDBObject(
"year", new BasicDBObject("$year", "$receivedDate")).append(
"month", new BasicDBObject("$month", "$receivedDate"))).append(
"count", new BasicDBObject("$sum", 1)));
}
};
newAggregation
is the static helper method which takes a list of pipeline stages.
Aggregation aggregation = newAggregation(group);
Finally, mongo template aggregate method to run the query.
List<FeedbackDateAggregation> res = mongoTemplate.aggregate(aggregation, "feedback", FeedbackDateAggregation.class).getMappedResults();
Generated Query:
{
"aggregate": "__collection__",
"pipeline": [{
"$group": {
"_id": {
"year": {
"$year": "$receivedDate"
},
"month": {
"$month": "$receivedDate"
}
},
"count": {
"$sum": 1
}
}
}]
}
Upvotes: 2
Reputation: 103355
You could try projecting the fields you want to use as the group by keys first by using the SpEL andExpression
in the projection operation and then group by the new fields in the group operation:
Aggregation agg = newAggregation(
project("id")
.andExpression("month(receivedDate)").as("month")
.andExpression("year(receivedDate)").as("year"),
group("year", "month").count().as("count")
);
AggregationResults<FeedbackDateAggregation> results = mongoTemplate.aggregate(
agg,
"feedback",
FeedbackDateAggregation.class
);
List<FeedbackDateAggregation> res = results.getMappedResults();
In the above a new aggregation via the newAggregation
static factory method is created to which you pass a list of aggregation operations. These aggregate operations define the aggregation pipeline of your Aggregation.
In the first step you project the fields from the input collection by using the SpEL andExpression
with the project operation.
In the second step you use the group operation to define a group for each "year"
and "month"
-value for which you aggregate the occurrence count via the count()
aggregation operator and collect the result in a new field called "count"
.
Finally call the aggregate()
Method on the MongoTemplate in order to let MongoDB perform the actual aggregation operation with the created Aggregation as an argument.
Upvotes: 1