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

Regrouper et compter à l'aide du cadre d'agrégation

Il semble que vous ayez commencé cela, mais vous vous êtes perdu dans certains des autres concepts. Il existe certaines vérités fondamentales lorsque vous travaillez avec des tableaux dans des documents, mais commençons là où vous vous étiez arrêté :

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 }
    }}
])

Donc ça va juste utiliser le $group pipeline pour rassembler vos documents sur les différentes valeurs du champ "status" et ensuite également produire un autre champ pour "count" qui bien sûr "compte" les occurrences de la clé de regroupement en passant une valeur de 1 au $sum opérateur pour chaque document trouvé. Cela vous amène à un point similaire à ce que vous décrivez :

{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }

C'est la première étape de ceci et assez facile à comprendre, mais maintenant vous devez savoir comment extraire les valeurs d'un tableau. Vous pourriez alors être tenté une fois que vous aurez compris la "notation par points" concept correctement pour faire quelque chose comme ceci :

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Mais ce que vous constaterez, c'est que le "total" sera en fait 0 pour chacun de ces résultats :

{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }

Pourquoi? Eh bien, les opérations d'agrégation MongoDB comme celle-ci ne traversent pas réellement les éléments du tableau lors du regroupement. Pour ce faire, le cadre d'agrégation a un concept appelé $unwind . Le nom est relativement explicite. Un tableau intégré dans MongoDB ressemble beaucoup à une association "un-à-plusieurs" entre des sources de données liées. Alors quoi $unwind fait est exactement ce genre de résultat de "jointure", où les "documents" résultants sont basés sur le contenu du tableau et les informations dupliquées pour chaque parent.

Donc, pour agir sur les éléments du tableau, vous devez utiliser $unwind première. Cela devrait logiquement vous amener à coder comme ceci :

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Et puis le résultat :

{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }

Mais ce n'est pas tout à fait ça ? Rappelez-vous ce que vous venez d'apprendre de $unwind et comment fait-il une jointure dénormalisée avec les informations parent? Alors maintenant, cela est dupliqué pour chaque document puisque les deux avaient deux membres de tableau. Ainsi, bien que le champ "total" soit correct, le "compte" est le double de ce qu'il devrait être dans chaque cas.

Un peu plus de précautions doivent être prises, donc au lieu de le faire en un seul $group étape, ça se fait en deux :

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }}
])

Qui obtient maintenant le résultat avec les totaux corrects :

{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }

Maintenant, les chiffres sont exacts, mais ce n'est toujours pas exactement ce que vous demandez. Je pense que vous devriez vous arrêter là car le type de résultat que vous attendez n'est vraiment pas adapté à un seul résultat de l'agrégation seule. Vous recherchez que le total soit "à l'intérieur" du résultat. Cela n'a vraiment pas sa place ici, mais sur de petites données, ça va :

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }},
    { "$group": {
        "_id": null,
        "data": { "$push": { "count": "$count", "total": "$total" } },
        "totalCost": { "$sum": "$total" }
    }}
])

Et un formulaire de résultat final :

{
    "_id" : null,
    "data" : [
            {
                    "count" : 1,
                    "total" : 350
            },
            {
                    "count" : 2,
                    "total" : 700
            }
    ],
    "totalCost" : 1050
}

Mais, "Ne faites pas ça" . MongoDB a une limite de document sur la réponse de 16 Mo, ce qui est une limitation de la spécification BSON. Sur de petits résultats, vous pouvez faire ce type d'emballage pratique, mais dans un schéma plus large, vous voulez les résultats dans le formulaire précédent et soit une requête distincte, soit vivre en itérant l'ensemble des résultats afin d'obtenir le total de tous les documents.

Vous semblez utiliser une version de MongoDB inférieure à 2.6 ou copier la sortie d'un shell RoboMongo qui ne prend pas en charge les dernières fonctionnalités de la version. À partir de MongoDB 2.6, les résultats de l'agrégation peuvent être un "curseur" plutôt qu'un seul tableau BSON. Ainsi, la réponse globale peut être bien supérieure à 16 Mo, mais uniquement lorsque vous ne compactez pas un seul document en tant que résultats, comme indiqué dans le dernier exemple.

Cela serait particulièrement vrai dans les cas où vous "paginiez" les résultats, avec des centaines à des milliers de lignes de résultats, mais vous vouliez juste qu'un "total" soit renvoyé dans une réponse API lorsque vous ne renvoyiez qu'une "page" de 25 résultats à un moment.

Quoi qu'il en soit, cela devrait vous donner un guide raisonnable sur la façon d'obtenir le type de résultats que vous attendez de votre formulaire de document commun. N'oubliez pas $unwind afin de traiter les tableaux, et généralement $group plusieurs fois afin d'obtenir des totaux à différents niveaux de regroupement à partir de vos regroupements de documents et de collections.