Използване на aggregate()
функция, можете да стартирате следния конвейер, който използва $sum
оператор, за да получите желаните резултати:
const results = await Cart.aggregate([
{ "$addFields": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]);
console.log(JSON.stringify(results, null, 4));
и съответната операция за актуализиране следва:
db.carts.updateMany(
{ },
[
{ "$set": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]
)
Или ако използвате MongoDB 3.2 и по-стари версии, където $sumкод>
е наличен само в етап $group, можете да направите
const pipeline = [
{ "$unwind": "$products" },
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"userPurchased": { "$first": "$userPurchased" },
"totalPrice": { "$sum": "$products.subTotal" }
}
}
]
Cart.aggregate(pipeline)
.exec(function(err, results){
if (err) throw err;
console.log(JSON.stringify(results, null, 4));
})
В горния конвейер първата стъпка е $unwind
оператор
{ "$unwind": "$products" }
което е доста удобно, когато данните се съхраняват като масив. Когато операторът unwind се приложи към поле с данни на списък, той ще генерира нов запис за всеки елемент от полето с данни на списъка, към който се прилага unwind. По същество изравнява данните.
Това е необходима операция за следващия етап на конвейера, $group
стъпка, в която групирате сплесканите документи по _id
поле, като по този начин ефективно прегрупира денормализираните документи обратно към оригиналната им схема.
$group
конвейерният оператор е подобен на SQL GROUP BY
клауза. В SQL не можете да използвате GROUP BY
освен ако не използвате някоя от функциите за агрегиране. По същия начин трябва да използвате и функция за агрегиране в MongoDB (наречена акумулатори). Можете да прочетете повече за акумулаторите тук
.
В тази $groupкод>
операция, логиката за изчисляване на totalPrice
и връщането на оригиналните полета е чрез акумулаторите . Получавате totalPrice
чрез сумиране на всеки отделен subTotal
стойности на група с $sum
като:
"totalPrice": { "$sum": "$products.subTotal }
Другият израз
"userPurchased": { "$first": "$userPurchased" },
ще върне userPurchased
стойност от първия документ за всяка група с помощта на $first
. По този начин се възстановява ефективно оригиналната схема на документа преди $unwind
Едно нещо, което трябва да се отбележи тук е, че когато се изпълнява конвейер, MongoDB прехвърля операторите един в друг. „Тръба“ тук приема значението на Linux:изходът на оператор става вход на следващия оператор. Резултатът от всеки оператор е нова колекция от документи. Така Mongo изпълнява горния тръбопровод по следния начин:
collection | $unwind | $group => result
Като странична бележка, за да помогнете с разбирането на тръбопровода или да го отстраните, ако получите неочаквани резултати, стартирайте агрегацията само с първия оператор на тръбопровода. Например, стартирайте агрегацията в mongo shell като:
db.cart.aggregate([
{ "$unwind": "$products" }
])
Проверете резултата, за да видите дали продуктите
масивът се деконструира правилно. Ако това даде очаквания резултат, добавете следното:
db.cart.aggregate([
{ "$unwind": "$products" },
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"userPurchased": { "$first": "$userPurchased" },
"totalPrice": { "$sum": "$products.subTotal" }
}
}
])
Повторете стъпките, докато стигнете до последната стъпка на тръбопровода.
Ако искате да актуализирате полето, можете да добавите $out
тръбопроводен етап като последна стъпка. Това ще запише получените документи от тръбопровода за агрегиране в същата колекция, като по този начин технически актуализира колекцията.
var pipeline = [
{ "$unwind": "$products" },
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"userPurchased": { "$first": "$userPurchased" },
"totalPrice": { "$sum": "$products.subTotal" }
}
},
{ "$out": "cart" } // write the results to the same underlying mongo collection
]
АКТУАЛИЗАЦИЯ
За да извършите както актуализацията, така и заявката, можете след това да подадете find()
извикайте обобщеното обратно извикване, за да получите актуализирания json, т.е.
Cart.aggregate(pipeline)
.exec(function(err, results){
if (err) throw err;
Cart.find().exec(function(err, docs){
if (err) return handleError(err);
console.log(JSON.stringify(docs, null, 4));
})
})
Използвайки Promises, можете да направите това алтернативно като
Cart.aggregate(pipeline).exec().then(function(res)
return Cart.find().exec();
).then(function(docs){
console.log(JSON.stringify(docs, null, 4));
});