Usando o
aggregate()
função, você pode executar o seguinte pipeline que usa o $sum
operador para obter os resultados desejados:const results = await Cart.aggregate([
{ "$addFields": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]);
console.log(JSON.stringify(results, null, 4));
e a operação de atualização correspondente segue:
db.carts.updateMany(
{ },
[
{ "$set": {
"totalPrice": {
"$sum": "$products.subTotal"
}
} },
]
)
Ou se estiver usando o MongoDB 3.2 e versões anteriores, onde
$sum
está disponível apenas na fase de grupos $, você pode fazer 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));
})
No pipeline acima, a primeira etapa é o
$unwind
operador { "$unwind": "$products" }
o que é bastante útil quando os dados são armazenados como uma matriz. Quando o operador de desenrolamento é aplicado em um campo de dados de lista, ele gerará um novo registro para cada elemento do campo de dados de lista no qual o desenrolamento é aplicado. Basicamente, achata os dados.
Esta é uma operação necessária para o próximo estágio do pipeline, o
$grupo
etapa em que você agrupa os documentos nivelados pelo _id
campo, reagrupando efetivamente os documentos desnormalizados de volta ao seu esquema original. O
$group
operador de pipeline é semelhante ao GROUP BY
do SQL cláusula. No SQL, você não pode usar GROUP BY
a menos que você use qualquer uma das funções de agregação. Da mesma forma, você também precisa usar uma função de agregação no MongoDB (chamada de acumuladores). Você pode ler mais sobre os aqui
. Neste
$group
operação, a lógica para calcular o totalPrice
e o retorno dos campos originais é feito por meio dos acumuladores . Você obtém ototalPrice
somando cada subTotal
individual valores por grupo com $sum
Como:"totalPrice": { "$sum": "$products.subTotal }
A outra expressão
"userPurchased": { "$first": "$userPurchased" },
retornará um
userPurchased
valor do primeiro documento para cada grupo usando $first
. Assim, reconstruindo efetivamente o esquema do documento original antes do $unwind
Uma coisa a notar aqui é ao executar um pipeline, o MongoDB canaliza os operadores uns para os outros. "Pipe" aqui tem o significado do Linux:a saída de um operador se torna a entrada do seguinte operador. O resultado de cada operador é uma nova coleção de documentos. Então o Mongo executa o pipeline acima da seguinte forma:
collection | $unwind | $group => result
Como uma observação lateral, para ajudar a entender o pipeline ou depurá-lo caso você obtenha resultados inesperados, execute a agregação apenas com o primeiro operador de pipeline. Por exemplo, execute a agregação no shell do mongo como:
db.cart.aggregate([
{ "$unwind": "$products" }
])
Verifique o resultado para ver se os
produtos
array é desconstruído corretamente. Se isso der o resultado esperado, adicione o seguinte:db.cart.aggregate([
{ "$unwind": "$products" },
{
"$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"userPurchased": { "$first": "$userPurchased" },
"totalPrice": { "$sum": "$products.subTotal" }
}
}
])
Repita as etapas até chegar à etapa final do pipeline.
Se você deseja atualizar o campo, pode adicionar o
$out
etapa de pipeline como a última etapa. Isso gravará os documentos resultantes do pipeline de agregação na mesma coleção, atualizando tecnicamente a coleção. 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
]
ATUALIZAÇÃO
Para fazer a atualização e a consulta, você pode emitir um
find()
chame o retorno de chamada agregado para obter o json atualizado, ou seja, 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));
})
})
Usando Promises, você pode fazer isso alternativamente como
Cart.aggregate(pipeline).exec().then(function(res)
return Cart.find().exec();
).then(function(docs){
console.log(JSON.stringify(docs, null, 4));
});