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

cuente las ocurrencias de matriz en todos los documentos con mongo

Personalmente, no soy un gran fanático de transformar "datos" como nombres de claves en un resultado. Los principios del marco de agregación tienden a estar de acuerdo ya que este tipo de operación tampoco es compatible.

Entonces, la preferencia personal es mantener los "datos" como "datos" y aceptar que la salida procesada es mejor y más lógica para un diseño de objeto consistente:

db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
])

Lo que produce un resultado como este:

[
    {
            "_id" : "female",
            "total" : 1,
            "hobbies" : [
                {
                    "name" : "tennis",
                    "count" : 1
                },
                {
                    "name" : "football",
                    "count" : 1
                }
            ]
    },
    {
        "_id" : "male",
        "total" : 2,
        "hobbies" : [
            {
                "name" : "swimming",
                "count" : 1
            },
            {
                "name" : "tennis",
                "count" : 2
            },
            {
                "name" : "football",
                "count" : 2
            }
        ]
    }
]

Así que el $group inicial hace la cuenta por "sexo" y acumula los pasatiempos en una serie de matrices. Luego, para desnormalizarte, $unwind dos veces para obtener elementos singulares, $group para obtener los totales por pasatiempo en cada sexo y finalmente reagrupar una matriz solo para cada sexo.

Son los mismos datos, tienen una estructura consistente y orgánica que es fácil de procesar, y MongoDB y el marco de agregación estuvieron muy contentos al producir este resultado.

Si realmente debe convertir sus datos en nombres de claves (y aun así le recomiendo que no lo haga, ya que no es un buen patrón a seguir en el diseño), entonces hacer tal transformación desde el estado final es bastante trivial para el procesamiento del código del cliente. Como ejemplo básico de JavaScript adecuado para el shell:

var out = db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
]).toArray();

out.forEach(function(doc) {
    var obj = {};
    doc.hobbies.sort(function(a,b) { return a.count < b.count });
    doc.hobbies.forEach(function(hobby) {
        obj[hobby.name] = hobby.count;
    });
    doc.hobbies = obj;
    printjson(doc);
});

Y luego, básicamente, está procesando cada resultado del cursor en el formulario de salida deseado, que en realidad no es una función de agregación que realmente se requiere en el servidor de todos modos:

{
    "_id" : "female",
    "total" : 1,
    "hobbies" : {
        "tennis" : 1,
        "football" : 1
    }
}
{
    "_id" : "male",
    "total" : 2,
    "hobbies" : {
        "tennis" : 2,
        "football" : 2,
        "swimming" : 1
    }
}

Donde eso también debería ser bastante trivial para implementar ese tipo de manipulación en el procesamiento de flujo del resultado del cursor para transformarlo según sea necesario, ya que es básicamente la misma lógica.

Por otro lado, siempre puede implementar toda la manipulación en el servidor usando mapReduce en su lugar:

db.people.mapReduce(
    function() {
        emit(
            this.sex,
            { 
                "total": 1,
                "hobbies": this.hobbies.map(function(key) {
                    return { "name": key, "count": 1 };
                })
            }
        );
    },
    function(key,values) {
        var obj  = {},
            reduced = {
                "total": 0,
                "hobbies": []
            };

        values.forEach(function(value) {
            reduced.total += value.total;
            value.hobbies.forEach(function(hobby) {
                if ( !obj.hasOwnProperty(hobby.name) )
                    obj[hobby.name] = 0;
                obj[hobby.name] += hobby.count;
            });
        });

        reduced.hobbies = Object.keys(obj).map(function(key) {
            return { "name": key, "count": obj[key] };
        }).sort(function(a,b) {
            return a.count < b.count;
        });

        return reduced;
    },
    { 
        "out": { "inline": 1 },
        "finalize": function(key,value) {
            var obj = {};
            value.hobbies.forEach(function(hobby) {
                obj[hobby.name] = hobby.count;
            });
            value.hobbies = obj;
            return value;
        }
    }
)

Donde mapReduce tiene su propio estilo distintivo de salida, pero los mismos principios se utilizan en la acumulación y manipulación, si no es tan eficiente como lo puede hacer el marco de agregación:

   "results" : [
        {
            "_id" : "female",
            "value" : {
                "total" : 1,
                "hobbies" : {
                    "football" : 1,
                    "tennis" : 1
                }
            }
        },
        {
            "_id" : "male",
            "value" : {
                "total" : 2,
                "hobbies" : {
                    "football" : 2,
                    "tennis" : 2,
                    "swimming" : 1
                }
            }
        }
    ]

Al final del día, sigo diciendo que la primera forma de procesamiento es la más eficiente y proporciona, en mi opinión, el funcionamiento más natural y consistente de la salida de datos, sin siquiera intentar convertir los puntos de datos en nombres de claves. Probablemente sea mejor considerar seguir ese patrón, pero si realmente debe hacerlo, existen formas de manipular los resultados en la forma deseada en varios enfoques de procesamiento.