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

compter les occurrences de tableau dans tous les documents avec mongo

Personnellement, je ne suis pas un grand fan de la transformation de "données" en noms de clés dans un résultat. Les principes du cadre d'agrégation ont tendance à s'accorder car ce type d'opération n'est pas pris en charge non plus.

Donc, la préférence personnelle est de maintenir les "données" en tant que "données" et d'accepter que la sortie traitée est en fait meilleure et plus logique pour une conception d'objet cohérente :

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" }
        }
    }}
])

Ce qui produit un résultat comme celui-ci :

[
    {
            "_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
            }
        ]
    }
]

Donc le $group initial fait le décompte par "sexe" et empile les passe-temps dans un tableau de tableaux. Ensuite pour vous dénormaliser $unwind deux fois pour obtenir des éléments singuliers, $group pour obtenir les totaux par passe-temps sous chaque sexe et enfin regrouper un tableau pour chaque sexe seul.

Ce sont les mêmes données, elles ont une structure cohérente et organique facile à traiter, et MongoDB et le cadre d'agrégation ont été très satisfaits de produire cette sortie.

Si vous devez vraiment convertir vos données en noms de clés (et je vous recommande toujours de ne pas le faire car ce n'est pas un bon modèle à suivre dans la conception), alors faire une telle transformation à partir de l'état final est assez trivial pour le traitement du code client. Comme exemple JavaScript de base adapté au 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);
});

Et puis vous traitez essentiellement chaque résultat de curseur dans le formulaire de sortie souhaité, ce qui n'est vraiment pas une fonction d'agrégation vraiment requise sur le serveur de toute façon :

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

Là où cela devrait également être assez simple d'implémenter ce type de manipulation dans le traitement de flux du résultat du curseur pour le transformer selon les besoins, car il s'agit essentiellement de la même logique.

En revanche, vous pouvez toujours implémenter toutes les manipulations sur le serveur en utilisant mapReduce à la place :

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

Où mapReduce a son propre style de sortie distinct, mais les mêmes principes sont utilisés dans l'accumulation et la manipulation, sinon probablement aussi efficaces que le cadre d'agrégation peut le faire :

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

En fin de compte, je dis toujours que la première forme de traitement est la plus efficace et fournit à mon avis le travail le plus naturel et le plus cohérent de la sortie de données, sans même tenter de convertir les points de données en noms de clés. Il est probablement préférable d'envisager de suivre ce modèle, mais si vous le devez vraiment, il existe des moyens de manipuler les résultats sous la forme souhaitée dans diverses approches de traitement.