2017-08-29 2 views
1

j'ai une collection MongoDB qui ressemble à ceci:comte et le groupe sur les occurrences de clés et leurs valeurs

[{ 
     "installer": "anthony", 
     "tester": "bob" 
    }, { 
     "installer": "chris", 
     "tester": "anthony" 
    }, { 
     "installer": "bob", 
     "tester": "dave" 
    }, { 
     "installer": "anthony", 
     "tester": "chris" 
    }, { 
     "installer": "chris", 
     "tester": "dave" 
    } 
] 

Je suis en train d'utiliser aggregate donc je peux compter combien de fois chaque nom apparaît dans chaque champ et récupérer le résultat suivant:

[{ 
     "name": "anthony", 
     "installer": 2, 
     "tester": 1 
    }, { 
     "name": "bob", 
     "installer": 1, 
     "tester": 1 
    }, { 
     "name": "chris", 
     "installer": 2, 
     "tester": 1 
    }, { 
     "name": "dave", 
     "installer": 0, 
     "tester": 2 
    } 
] 

Ceci est la requête que j'ai terminé à ce jour, le problème est qu'il ne retourne que le comte name et installer sans le comte tester. Je pourrais courir cette question deux fois (un pour installer et un pour tester) mais je voudrais trouver un moyen de retourner les deux chefs à la fois.

db.data.aggregate([ 
    { 
     "$group": { 
      "_id": "$installer", 
      "installer": { "$sum": 1 } 
     }, 
     "$project": { 
      "name": "$_id", 
      "installer": 1, 
      "_id": 0 
     } 
    } 
]) 

Quels sont les changements à ma question sont nécessaires pour que je puisse obtenir les deux installer et tester compte de chaque personne?

+0

Cela devrait vous aider: https://stackoverflow.com/questions/18501064/mongodb-aggregation-counting-distinct-fields –

Répondre

2

Vous voulez essentiellement $cond pour choisir de passer 1 ou 0 à l'accumulateur $sum dans la canalisation $group, et une valeur initiale comme un « tableau » pour les deux champs à l'aide $unwind pour créer une copie du document pour chaque personne.

db.data.aggregate([ 
    { "$addFields": { 
    "val": ["$installer","$tester"]  
    }}, 
    { "$unwind": "$val" }, 
    { "$group": { 
    "_id": { "_id": "$_id", "val": "$val" }, 
    "installer": { 
     "$max": { 
     "$cond": [ 
      { "$eq": ["$installer","$val"] }, 
      1, 
      0 
     ] 
     }  
    }, 
    "tester": { 
     "$max": { 
     "$cond": [ 
      { "$eq": ["$tester","$val"] }, 
      1, 
      0 
     ] 
     }  
    } 
    }}, 
    { "$group": { 
    "_id": "$_id.val", 
    "installer": { "$sum": "$installer" }, 
    "tester": { "$sum": "$tester" } 
    }} 
]) 

Pour contrer le cas où un document donné pourrait avoir deux le même « installateur » et les valeurs « testeur » nous devrions effectivement agréger sur le « document » par l'émis « val » comme une première étape. L'utilisation du $cond à l'intérieur d'un accumulateur $max fait de ce cas un document "unique" au lieu de "deux", soit un pour chaque entrée de tableau.

L'autre cas est bien sûr de revenir simplement « l'ensemble » des valeurs en appliquant $setUnion contre la liste initiale pour éviter la duplication dans un tel cas:

db.data.aggregate([ 
    { "$addFields": { 
    "val": { "$setUnion": [["$installer","$tester"]] } 
    }}, 
    { "$unwind": "$val" }, 
    { "$group": { 
    "_id": "$val", 
    "installer": { 
     "$sum": { 
     "$cond": [ 
      { "$eq": ["$installer","$val"] }, 
      1, 
      0 
     ] 
     }  
    }, 
    "tester": { 
     "$sum": { 
     "$cond": [ 
      { "$eq": ["$tester","$val"] }, 
      1, 
      0 
     ] 
     }  
    } 
    }} 
]) 

j'ai ajouté un document à votre source comme :

{ "installer": "jack", "tester": "jack" } 

Afin d'illustrer le résultat.

Quant à $cond, il est un « ternaire » ou if..then..else condition, où les arguments sont « première » if pour une condition à évaluer en tant que valeur booléenne, then étant la valeur de retour quand une valeur true et else pour revenir lorsque la condition est false.

Il peut être tour à tour écrit comme:

"$cond": { 
    "if": { "$eq": ["$installer","$val"] }, 
    "then": 1, 
    "else": 0 
} 

Mais la syntaxe « tableau » d'origine est un peu plus court pour écrire des expressions simples. La plupart des gens reconnaîtraient toujours le "ternaire" pour ce qu'il est, mais si vous pensez que cela rend le code plus clair, vous pouvez utiliser le formulaire "clés nommées" à la place.

Le résultat est bien sûr le 1 est seulement retourné lorsque le champ est présent dans le document, ce qui donne les comptes corrects:

/* 1 */ 
{ 
    "_id" : "jack", 
    "installer" : 1.0, 
    "tester" : 1.0 
} 

/* 2 */ 
{ 
    "_id" : "dave", 
    "installer" : 0.0, 
    "tester" : 2.0 
} 

/* 3 */ 
{ 
    "_id" : "bob", 
    "installer" : 1.0, 
    "tester" : 1.0 
} 

/* 4 */ 
{ 
    "_id" : "chris", 
    "installer" : 2.0, 
    "tester" : 1.0 
} 

/* 5 */ 
{ 
    "_id" : "anthony", 
    "installer" : 2.0, 
    "tester" : 1.0 
} 

Ajout du « tableau » initial du document peut alternativement être fait en utilisant $project si votre version MongoDB ne prend pas en charge $addFields. La seule différence est « explicitement », y compris les autres domaines qui sont nécessaires plus tard:

{ "$project": { 
    "tester": 1, 
    "installer": 1, 
    "val": { "$setUnion": [["$installer","$tester"]] } 
}} 

Et si votre MongoDB est toujours en fait plus que MongoDB 3.2 qui permet que la notation d'un « tableau », vous pouvez alors utiliser à la place $map de MongoDB 2,6 et vers le haut:

{ "$project": { 
    "tester": 1, 
    "installer": 1, 
    "val": { 
    "$setUnion": [ 
     { "$map": { 
     "input": ["A","B"], 
     "as": "a", 
     "in": { 
      "$cond": [{ "$eq": ["$$a", "A"] }, "$installer", "$tester"] 
     } 
     } 
    ] 
    } 
}} 

Encore une fois en utilisant $cond pour sélectionner alternativement la valeur à présent comme éléments du tableau.

En outre, vous devriez vraiment éviter de faire des choses comme l'ajout d'un $project à la fin des instructions. Vous pouvez bien sûr le faire, mais cela signifie que tous les résultats de l'étape précédente du pipeline sont «réexécutés» afin d'effectuer les changements supplémentaires. Pour quelque chose d'aussi trivial que de changer "_id" à "name", il est généralement préférable d'accepter simplement que la "clé de regroupement" est appelée _id et de laisser cela.

À la suite de $group, il fait est le « identifiant unique » pour lequel _id est la nomenclature commune.

+0

Merci pour la réponse et l'explication détaillée – dat3450

+0

Il semble que la requête double les nombres réels, donc au lieu de obtenir '" installer ": 2' et' "tester": 1' pour 'anthony', il retourne respectivement' 4' et '2'. – dat3450

+0

Cela se produit lorsque 'installer' et' tester' sont la même personne, par ex. '" installer ":" bob "," testeur ": bob' – dat3450