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

Calcule el promedio de campos en documentos/matriz incrustados

El marco de agregación en MongoDB 3.4 y versiones posteriores ofrece el $reduce operador que calcula eficientemente el total sin necesidad de canalizaciones adicionales. Considere usarlo como una expresión para devolver las calificaciones totales y obtener la cantidad de calificaciones usando $size . Junto con $addFields , el promedio se puede calcular usando el operador aritmético $divide como en la fórmula average = total ratings/number of ratings :

db.collection.aggregate([
    { 
        "$addFields": { 
            "rating_average": {
                "$divide": [
                    { // expression returns total
                        "$reduce": {
                            "input": "$ratings",
                            "initialValue": 0,
                            "in": { "$add": ["$$value", "$$this.rating"] }
                        }
                    },
                    { // expression returns ratings count
                        "$cond": [
                            { "$ne": [ { "$size": "$ratings" }, 0 ] },
                            { "$size": "$ratings" }, 
                            1
                        ]
                    }
                ]
            }
        }
    }           
])

Salida de muestra

{
    "_id" : ObjectId("58ab48556da32ab5198623f4"),
    "title" : "The Hobbit",
    "ratings" : [ 
        {
            "title" : "best book ever",
            "rating" : 5.0
        }, 
        {
            "title" : "good book",
            "rating" : 3.5
        }
    ],
    "rating_average" : 4.25
}

Con versiones anteriores, primero debe aplicar el $unwind operador en las ratings campo de matriz primero como su paso de canalización de agregación inicial. Esto deconstruirá las ratings campo de matriz de los documentos de entrada para generar un documento para cada elemento. Cada documento de salida reemplaza la matriz con un valor de elemento.

La segunda etapa de canalización sería el $group operador que agrupa los documentos de entrada por el _id y title expresión de identificador de teclas y aplica el $avg deseado expresión del acumulador a cada grupo que calcula el promedio. Hay otro operador acumulador $push que conserva el campo de matriz de calificaciones original al devolver una matriz de todos los valores que resultan de aplicar una expresión a cada documento en el grupo anterior.

El último paso de canalización es el $project operador que luego remodela cada documento en la secuencia, por ejemplo, agregando el nuevo campo ratings_average .

Entonces, si por ejemplo tiene un documento de muestra en su colección (como arriba y abajo):

db.collection.insert({
    "title": "The Hobbit",

    "ratings": [
        {
            "title": "best book ever",
            "rating": 5
        },
        {
            "title": "good book",
            "rating": 3.5
        }
    ]
})

Para calcular el promedio de la matriz de calificaciones y proyectar el valor en otro campo ratings_average , luego puede aplicar la siguiente canalización de agregación:

db.collection.aggregate([
    {
        "$unwind": "$ratings"
    },
    {
        "$group": {
            "_id": {
                "_id": "$_id",
                "title": "$title"
            },
            "ratings":{
                "$push": "$ratings"
            },
            "ratings_average": {
                "$avg": "$ratings.rating"
            }
        }
    },
    {
        "$project": {
            "_id": 0,
            "title": "$_id.title",
            "ratings_average": 1,
            "ratings": 1
        }
    }
])

Resultado :

/* 1 */
{
    "result" : [ 
        {
            "ratings" : [ 
                {
                    "title" : "best book ever",
                    "rating" : 5
                }, 
                {
                    "title" : "good book",
                    "rating" : 3.5
                }
            ],
            "ratings_average" : 4.25,
            "title" : "The Hobbit"
        }
    ],
    "ok" : 1
}