Вычисляемые по группам поля в MongoDB
для этого примера из документации MongoDB, как написать запрос с помощью MongoTemplate?
db.sales.aggregate(
[
{
$group : {
_id : { month: { $month: "$date" }, day: { $dayOfMonth: "$date" }, year: { $year: "$date" } },
totalPrice: { $sum: { $multiply: [ "$price", "$quantity" ] } },
averageQuantity: { $avg: "$quantity" },
count: { $sum: 1 }
}
}
]
)
или вообще, как группировать по вычисляемому полю?
2 ответов
вы действительно можете сделать что-то подобное с "проектом" сначала, но для меня это немного контр-интуитивно требовать $project
этап перед рукой:
Aggregation agg = newAggregation(
project("quantity")
.andExpression("dayOfMonth(date)").as("day")
.andExpression("month(date)").as("month")
.andExpression("year(date)").as("year")
.andExpression("price * quantity").as("totalAmount"),
group(fields().and("day").and("month").and("year"))
.avg("quantity").as("averavgeQuantity")
.sum("totalAmount").as("totalAmount")
.count().as("count")
);
как я уже сказал, контринтуитивно, так как вы должны просто быть в состоянии объявить все это под $group
этап, но помощники, похоже, не работают таким образом. Сериализация выходит немного забавной (обертывает аргументы оператора даты массивами), но она, похоже, работает. Но все же, это двух стадиях, а не один.
в чем проблема с этим? Ну, разделяя этапы на этапы, часть "проект" заставляет обработку всех документов в конвейере, чтобы получить вычисляемые поля, то есть она проходит через все, прежде чем перейти к групповой стадии.
разницу во времени обработки можно четко увидеть, выполнив запросы в обеих формах. С отдельным этапом проекта, на моем оборудовании занимает три раза дольше, чем запрос, в котором все поля вычисляются во время операции "группа".
таким образом, кажется, единственный способ построить это правильно-это построить объект трубопровода самостоятельно:
ApplicationContext ctx =
new AnnotationConfigApplicationContext(SpringMongoConfig.class);
MongoOperations mongoOperation = (MongoOperations) ctx.getBean("mongoTemplate");
BasicDBList pipeline = new BasicDBList();
String[] multiplier = { "$price", "$quantity" };
pipeline.add(
new BasicDBObject("$group",
new BasicDBObject("_id",
new BasicDBObject("month", new BasicDBObject("$month", "$date"))
.append("day", new BasicDBObject("$dayOfMonth", "$date"))
.append("year", new BasicDBObject("$year", "$date"))
)
.append("totalPrice", new BasicDBObject(
"$sum", new BasicDBObject(
"$multiply", multiplier
)
))
.append("averageQuantity", new BasicDBObject("$avg", "$quantity"))
.append("count",new BasicDBObject("$sum",1))
)
);
BasicDBObject aggregation = new BasicDBObject("aggregate","collection")
.append("pipeline",pipeline);
System.out.println(aggregation);
CommandResult commandResult = mongoOperation.executeCommand(aggregation);
или если все это кажется вам кратким, то вы всегда можете работать с источником JSON и анализировать это. Но, конечно, он должен быть действительным JSON:
String json = "[" +
"{ \"$group\": { "+
"\"_id\": { " +
"\"month\": { \"$month\": \"$date\" }, " +
"\"day\": { \"$dayOfMonth\":\"$date\" }, " +
"\"year\": { \"$year\": \"$date\" } " +
"}, " +
"\"totalPrice\": { \"$sum\": { \"$multiply\": [ \"$price\", \"$quantity\" ] } }, " +
"\"averageQuantity\": { \"$avg\": \"$quantity\" }, " +
"\"count\": { \"$sum\": 1 } " +
"}}" +
"]";
BasicDBList pipeline = (BasicDBList)com.mongodb.util.JSON.parse(json);
Другой альтернативой является использование пользовательского класса операции агрегации, определенного следующим образом:
public class CustomAggregationOperation implements AggregationOperation {
private DBObject operation;
public CustomAggregationOperation (DBObject operation) {
this.operation = operation;
}
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}
затем реализуйте его в конвейере следующим образом:
Aggregation aggregation = newAggregation(
new CustomAggregationOperation(
new BasicDBObject(
"$group",
new BasicDBObject("_id",
new BasicDBObject("day", new BasicDBObject("$dayOfMonth", "$date" ))
.append("month", new BasicDBObject("$month", "$date"))
.append("year", new BasicDBObject("$year", "$date"))
)
.append("totalPrice", new BasicDBObject(
"$sum", new BasicDBObject(
"$multiply", Arrays.asList("$price","$quantity")
)
))
.append("averageQuantity", new BasicDBObject("$avg", "$quantity"))
.append("count",new BasicDBObject("$sum",1))
)
)
)
таким образом, это в основном просто интерфейс, который согласуется с тем, что используется существующими помощниками конвейера, но вместо использования других вспомогательных методов он напрямую принимает DBObject
определение для возврата при строительстве трубопровода.