Använda aggregate()
kan du köra följande pipeline som använder $summa
för att få önskat resultat:
const results = await Cart.aggregate([
{ "$addFields": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]);
console.log(JSON.stringify(results, null, 4));
och motsvarande uppdateringsåtgärd följer:
db.carts.updateMany(
{ },
[
{ "$set": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]
)
Eller om du använder MongoDB 3.2 och tidigare versioner, där $sum
är endast tillgänglig i $group-stadiet, du kan göra
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));
})
I ovanstående pipeline är det första steget $unwind
operatör
{ "$unwind": "$products" }
vilket är ganska praktiskt när data lagras som en array. När avvecklingsoperatorn appliceras på ett listdatafält, kommer den att generera en ny post för varje element i listdatafältet som avveckling tillämpas på. Det plattar i princip ut data.
Detta är en nödvändig operation för nästa steg i pipeline, $grupp
steg där du grupperar de tillplattade dokumenten efter _id
fältet, vilket effektivt omgrupperar de denormaliserade dokumenten tillbaka till deras ursprungliga schema.
$group
pipeline-operatorn liknar SQL:s GROUP BY
klausul. I SQL kan du inte använda GROUP BY
såvida du inte använder någon av aggregeringsfunktionerna. På samma sätt måste du också använda en aggregeringsfunktion i MongoDB (kallas ackumulatorer). Du kan läsa mer om ackumulatorerna här
.
I denna $group
operation, logiken för att beräkna totalPrice
och de ursprungliga fälten returneras genom ackumulatorerna . Du får totalPrice
genom att summera varje enskild subTotal
värden per grupp med $sum
som:
"totalPrice": { "$sum": "$products.subTotal }
Det andra uttrycket
"userPurchased": { "$first": "$userPurchased" },
returnerar en userPurchased
värde från det första dokumentet för varje grupp med $first
. På så sätt återuppbyggs det ursprungliga dokumentschemat före $unwind
En sak att notera här är när man kör en pipeline, MongoDB kopplar operatörer till varandra. "Pipe" har här Linux-betydelsen:utdata från en operatör blir indata för följande operatör. Resultatet av varje operatör är en ny samling dokument. Så Mongo kör ovanstående pipeline enligt följande:
collection | $unwind | $group => result
Som en sidoanteckning, för att hjälpa dig att förstå pipelinen eller för att felsöka den om du får oväntade resultat, kör aggregeringen med bara den första pipeline-operatören. Kör till exempel aggregeringen i mongo-skalet som:
db.cart.aggregate([
{ "$unwind": "$products" }
])
Kontrollera resultatet för att se om produkterna
arrayen är korrekt dekonstruerad. Om det ger det förväntade resultatet, lägg till nästa:
db.cart.aggregate([
{ "$unwind": "$products" },
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"userPurchased": { "$first": "$userPurchased" },
"totalPrice": { "$sum": "$products.subTotal" }
}
}
])
Upprepa stegen tills du kommer till det sista pipelinesteget.
Om du vill uppdatera fältet kan du lägga till $out
pipeline skede som det sista steget. Detta kommer att skriva de resulterande dokumenten för aggregeringspipelinen till samma samling, vilket tekniskt uppdaterar samlingen.
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
]
UPPDATERA
För att göra både uppdateringen och frågan kan du sedan utfärda en find()
ring in den samlade återuppringningen för att få den uppdaterade json, dvs.
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));
})
})
Med Promises kan du göra detta alternativt som
Cart.aggregate(pipeline).exec().then(function(res)
return Cart.find().exec();
).then(function(docs){
console.log(JSON.stringify(docs, null, 4));
});