MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Soma de subdocumentos no Mongoose


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));
});