sql >> Base de Datos >  >> NoSQL >> MongoDB

Suma de subdocumentos en Mongoose

Uso de aggregate() puede ejecutar la siguiente canalización que utiliza $sum operador para obtener los resultados deseados:

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

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

y la operación de actualización correspondiente sigue:

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

O si usa MongoDB 3.2 y versiones anteriores, donde $sum está disponible solo en la fase de grupos $, puedes hacerlo

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

En la canalización anterior, el primer paso es $unwind operador

{ "$unwind": "$products" }

lo que resulta bastante útil cuando los datos se almacenan como una matriz. Cuando el operador de desenrollado se aplica en un campo de datos de lista, generará un nuevo registro para todos y cada uno de los elementos del campo de datos de lista en el que se aplica el desenrollado. Básicamente aplana los datos.

Esta es una operación necesaria para la siguiente etapa de canalización, el $group paso donde agrupa los documentos acoplados por el _id campo, reagrupando así de manera efectiva los documentos desnormalizados de regreso a su esquema original.

El $group El operador de canalización es similar al GROUP BY de SQL. cláusula. En SQL, no puede usar GROUP BY a menos que utilice alguna de las funciones de agregación. De la misma manera, también debe usar una función de agregación en MongoDB (llamada acumuladores). Puede leer más sobre los acumuladores aquí .

En este $group operación, la lógica para calcular el totalPrice y devolver los campos originales es a través de los acumuladores . Obtienes el totalPrice sumando cada subTotal individual valores por grupo con $sum como:

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

La otra expresión

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

devolverá un userPurchased valor del primer documento para cada grupo usando $first . Por lo tanto, reconstruye efectivamente el esquema del documento original antes del $unwind

Una cosa a tener en cuenta aquí es que cuando se ejecuta una canalización, MongoDB canaliza a los operadores entre sí. "Pipe" aquí toma el significado de Linux:la salida de un operador se convierte en la entrada del siguiente operador. El resultado de cada operador es una nueva colección de documentos. Entonces Mongo ejecuta la canalización anterior de la siguiente manera:

collection | $unwind | $group => result

Como nota al margen, para ayudar a comprender la canalización o para depurarla si obtiene resultados inesperados, ejecute la agregación solo con el primer operador de canalización. Por ejemplo, ejecute la agregación en mongo shell como:

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

Verifique el resultado para ver si los products matriz se deconstruye correctamente. Si eso da el resultado esperado, agregue lo siguiente:

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

Repita los pasos hasta que llegue al último paso de canalización.

Si desea actualizar el campo, puede agregar $out etapa de canalización como último paso. Esto escribirá los documentos resultantes de la tubería de agregación en la misma colección, actualizando técnicamente la colección.

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
]

ACTUALIZAR

Para realizar tanto la actualización como la consulta, puede emitir un find() llame a la devolución de llamada agregada para obtener el json actualizado, es decir,

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, puede hacer esto alternativamente como

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