MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Somme des sous-documents dans Mongoose

Utilisation de aggregate() fonction, vous pouvez exécuter le pipeline suivant qui utilise le $sum opérateur pour obtenir les résultats souhaités :

const results = await Cart.aggregate([
    { "$addFields": {
        "totalPrice": {
            "$sum": "$products.subTotal"
        }
    } },
]);

console.log(JSON.stringify(results, null, 4));

et l'opération de mise à jour correspondante suit :

db.carts.updateMany(
   { },
   [
        { "$set": {
            "totalPrice": {
                "$sum": "$products.subTotal"
            }
        } },
    ]
)

Ou si vous utilisez MongoDB 3.2 et des versions antérieures, où $sum n'est disponible qu'en $étape de groupe, vous pouvez le faire

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

Dans le pipeline ci-dessus, la première étape est le $unwind opérateur

{ "$unwind": "$products" }

ce qui est très pratique lorsque les données sont stockées sous forme de tableau. Lorsque l'opérateur de déroulement est appliqué sur un champ de données de liste, il génère un nouvel enregistrement pour chaque élément du champ de données de liste sur lequel le déroulement est appliqué. Il aplatit essentiellement les données.

Il s'agit d'une opération nécessaire pour la prochaine étape du pipeline, le $group étape où vous regroupez les documents aplatis par le _id champ, regroupant ainsi efficacement les documents dénormalisés dans leur schéma d'origine.

Le $group l'opérateur de pipeline est similaire au GROUP BY du SQL clause. En SQL, vous ne pouvez pas utiliser GROUP BY sauf si vous utilisez l'une des fonctions d'agrégation. De la même manière, vous devez également utiliser une fonction d'agrégation dans MongoDB (appelée accumulateurs). Vous pouvez en savoir plus sur les accumulateurs ici .

Dans ce $group opération, la logique pour calculer le totalPrice et le retour des champs d'origine se fait via les accumulateurs . Vous obtenez le totalPrice en additionnant chaque subTotal individuel valeurs par groupe avec $sum comme :

"totalPrice": { "$sum": "$products.subTotal }

L'autre expression

"userPurchased": { "$first": "$userPurchased" },

renverra un userPurchased valeur du premier document pour chaque groupe en utilisant $first . Reconstruisant ainsi efficacement le schéma du document d'origine avant le $unwind

Une chose à noter ici est que lors de l'exécution d'un pipeline, MongoDB relie les opérateurs les uns aux autres. "Pipe" prend ici le sens Linux :la sortie d'un opérateur devient l'entrée de l'opérateur suivant. Le résultat de chaque opérateur est une nouvelle collection de documents. Mongo exécute donc le pipeline ci-dessus comme suit :

collection | $unwind | $group => result

En remarque, pour vous aider à comprendre le pipeline ou pour le déboguer si vous obtenez des résultats inattendus, exécutez l'agrégation avec uniquement le premier opérateur de pipeline. Par exemple, exécutez l'agrégation dans mongo shell en tant que :

db.cart.aggregate([
    { "$unwind": "$products" }
])

Vérifiez le résultat pour voir si les products tableau est déconstruit correctement. Si cela donne le résultat attendu, ajoutez le suivant :

db.cart.aggregate([
    { "$unwind": "$products" },
    {
        "$group": {
            "_id": "$_id",
            "products": { "$push": "$products" },
            "userPurchased": { "$first": "$userPurchased" },
            "totalPrice": { "$sum": "$products.subTotal" }
        }
    }
])

Répétez les étapes jusqu'à ce que vous arriviez à l'étape finale du pipeline.

Si vous souhaitez mettre à jour le champ, vous pouvez ajouter le $out l'étape du pipeline comme dernière étape. Cela écrira les documents résultants du pipeline d'agrégation dans la même collection, mettant ainsi techniquement à jour la collection.

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
]

MISE À JOUR

Pour faire à la fois la mise à jour et la requête, vous pouvez alors émettre un find() appelez le rappel agrégé pour obtenir le json mis à jour, c'est-à-dire

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

En utilisant Promises, vous pouvez le faire alternativement comme

Cart.aggregate(pipeline).exec().then(function(res)
    return Cart.find().exec();
).then(function(docs){  
    console.log(JSON.stringify(docs, null, 4));
});