mongo group query comment conserver les champs

97

Tout le monde. Dans mongo group query, le résultat affiche uniquement la ou les clés dans les arguments. Comment conserver le premier document de chaque groupe comme le groupe de requêtes mysql. par exemple:

-------------------------------------------------------------------------
|  name  | age  |  sex  | province |   city   |   area   |   address     |
-------------------------------------------------------------------------
| ddl1st | 22   | 纯爷们 |  BeiJing |  BeiJing | ChaoYang | QingNianLu    |
| ddl1st | 24   | 纯爷们 |  BeiJing |  BeiJing | XuHui    | ZhaoJiaBangLu |
|  24k   | 220  | ...   |  ....    |  ...     | ...      | ...           |
-------------------------------------------------------------------------



db.users.group({key: { name: 1},reduce: function ( curr, result ) { result.count ++ },initial: {count : 0 } })

résultat:

[
{
    "name" : "ddl1st",
    "count" : 1
},
{
    "name" : "24k",
    "count" : 1
}
]

Comment obtenir les éléments suivants:

[
   {
   "name" : "ddl1st",
   "age" : 22,
   "sex" : "纯爷们",
   "province" : "BeiJing",
   "city" : "BeiJing",
   "area" : "ChaoYang",
   "address" : "QingNianLu",
   "count" : 1
   },
   {
   "name" : "24k",
   "age" : 220,
   "sex" : "...",
   "province" : "...",
   "city" : "...",
   "area" : "...",
   "address" : "...",
   "count" : 1
}
]
plusor
la source

Réponses:

215

Si vous souhaitez conserver les informations sur les premières entrées correspondantes pour chaque groupe, vous pouvez essayer d'agréger comme:

db.test.aggregate({
  $group: {
   _id : '$name',
   name : { $first: '$name' },
   age : { $first: '$age' },
   sex : { $first: '$sex' },
   province : { $first: '$province' },
   city : { $first: '$city' },
   area : { $first: '$area' },
   address : { $first: '$address' },
   count : { $sum: 1 }
  }
}
MervS
la source
3
Pourquoi avez-vous besoin de {$first: '$age'}etc.? Est-il possible d'avoir juste age: $age?
lightalchemist
7
@lightalchemist Ce n'est pas possible. C'est une sorte d'astuce pour faire savoir au «groupe» quoi choisir.
TechWisdom
4
Et si, au lieu de compter, cette agrégation faisait un $ max ou $ min pour l'âge? Le $ first ne correspondra pas nécessairement à l'âge minimum ou maximum trouvé pour les autres champs. Comment gérer cela alors?
Juliomac
2
Cela ne fonctionne pas, il regroupe par les autres champs ce qui est indésirable.
Jack Cole
1
@Juliomac, je crois que si votre sortie souhaitée est $ max / $ min et en conservant les champs qui ne sont pas dans le $group_id, vous pouvez $sortauparavant avec le champ souhaité, puis regrouper et utiliser $firstou $lastopérateurs sur n'importe quel champ. Lors de l'accumulation, l'idée d'inclure d'autres champs (qui sont accumulés / canalisés / réduits) n'a pas beaucoup de sens, même théoriquement. Cependant, le tri préalable est en fait inefficace par rapport au tri de chaque groupe en eux-mêmes car les algorithmes de tri sont plus complexes que O (n). Je souhaite qu'il y ait de meilleures façons dans MongoDB.
Vemulo
16

Au fait, si vous souhaitez conserver non seulement le premier document, vous pouvez utiliser $ addToSet Par exemple:

db.test.aggregate({
  $group: {
    _id: '$name',
    name : { $addToSet: '$name' }
    age : { $addToSet: '$age' },
    count: { $sum: 1 }
  }
}
陈 学 铄
la source
1
Merci! Je l'ai mieux (évitez de gâcher la commande avec un ensemble): data: {$ addToSet: {name: '$ name', _id: '$ _id', age: '$ age'}}
Benoit
14

[modifié pour inclure des suggestions de commentaires]

Je suis venu ici à la recherche d'une réponse mais je n'étais pas satisfait de la réponse choisie (surtout compte tenu de son âge). J'ai trouvé cette réponse qui est une meilleure solution (adaptée):

db.test.aggregate({
  $group: {
    _id: '$name',
   person: { "$first": "$$ROOT" },
   count: { $sum: 1 }
  },
  {
    "$replaceRoot": { "newRoot": { "$mergeObjects": ["$person", { count: "$count" }]} }
  }
}
Pieter
la source
3
MAIS, vous perdez le countchamp. Vous devez l'utiliser $mergeObjectspour le garder.
0zkr PM
1
Pour élaborer sur le commentaire de 0zkr sur l'utilisation de $ mergeObjects et aider les autres avec la syntaxe, la dernière syntaxe du pipeline serait{"$replaceRoot": {"newRoot": {"$mergeObjects": ["$person", {count: "$count"}]}}}
Jerren Saunders le
7

Vous pouvez essayer ceci

db.test.aggregate({
      { $group: 
            { _id: '$name',count: { $sum: 1 }, data: { $push: '$$ROOT' } } },
      {
        $project: {
          _id:0,
          data:1,
          count :1
        }
      }

}
Deeksha Sharma
la source
4

C'est ce que j'ai fait, ça marche bien.

db.person.aggregate([
{
  $group: { _id: '$name'}, // pass the set of field to be grouped
   age : { $first: '$age' }, // retain remaining field
   count: { $sum: 1 } // count based on your group
},
{
  $project:{
       name:"$_id.name",
       age: "$age",
       count: "$count",
       _id:0 
  }
}])
Thavaprakash Swaminathan
la source
4

Juste une mise à jour rapide si l'on rencontre le même problème avec des documents avec de nombreux champs. On peut utiliser la puissance de la combinaison de l' $replaceRootétage du $mergeObjectspipeline et de l' exploitant du pipeline.

db.users.aggregate([
  {
    $group: {
      _id: '$name',
      user: { $first: '$$ROOT' },
      count: { $sum: 1 }
    },
  },
  {
    $replaceRoot: {
      newRoot: { $mergeObjects: [{ count: '$count' }, '$user'] }
    }
  }
])
Gary Wild
la source
4

Utiliser $firstavec le $$ROOTdocument, puis utiliser $replaceRootavec le premier champ.

db.test.aggregate([
  { "$group": {
    "_id": "$name",
    "doc": { "$first": "$$ROOT" }
  }},
  { "$replaceRoot": { "newRoot": "$doc" }}
])
Ashh
la source
C'était tellement utile! Merci!! Je cherchais depuis un moment, mais je ne trouvais pas exactement ce dont j'avais besoin. C'était parfait!
The Student Soul le
Cette réponse est «directe» et parfaite. Merci!
M. Nunisa
1

Je ne connaissais pas .grouphelper, mais si vous préférez utiliser le Framework d'agrégation , vous devrez spécifier les champs à renvoyer. Corrigez-moi si je me trompe, mais en SQL, vous devrez le faire de toute façon.

Eh bien, voici comment vous le feriez avec le cadre d'agrégation mentionné précédemment:

db.test.aggregate({
  $group: {
    _id: { name: "$name", city: "$city", fieldName: "$fieldName" },
    count: { $sum: 1 }
  }
})
Gustavohenke
la source
10
Merci pour ton aide. dans cette requête, les champs sont spécifiés par groupe, je veux juste regrouper par un champ, puis les autres spécifient des champs. une bonne idée?
ou
1

J'ai créé cette fonction pour généraliser l'inversion d'une étape de déroulement ... faites-moi savoir si vous rencontrez des bugs avec elle, mais cela fonctionne bien pour moi!

const createReverseUnwindStages = unwoundField => {
  const stages = [
    //
    // Group by the unwound field, pushing each unwound value into an array,
    //
    // Store the data from the first unwound document
    // (which should all be the same apart from the unwound field)
    // on a field called data.
    // This is important, since otherwise we have to specify every field we want to keep individually.
    //
    {
      $group: {
        _id: '$_id',
        data: {$first: '$$ROOT'},
        [unwoundField]: {$push: `$${unwoundField}`},
      },
    },

    //
    // Copy the array of unwound fields resulting from the group into the data object,
    // overwriting the singular unwound value
    //
    {
      $addFields: {[`data.${unwoundField}`]: `$${unwoundField}`},
    },

    //
    // Replace the root with our data object
    //
    {
      $replaceRoot: {
        newRoot: '$data',
      },
    },
  ]

  return stages
}
Matt Wills
la source
Le meilleur lorsque les documents de la même collection ont différents noms de champ.
user7364588
0

Si vous souhaitez projeter tous les champs, utilisez la requête ci-dessous.

db.persons.aggregate({
      { $group: { _id: '$name', data: { $push: '$$ROOT' }, total: { $sum: 1 }} },
      {
        $project: {
          _id:0,
          data:1,
          total :1
        }
      }
}
Sameer
la source
-1

Voici la réponse >>>>

    $m = new \MongoDB\Driver\Manager();

    $command = new \MongoDB\Driver\Command([
        'aggregate' => 'mytestusers',
        'pipeline' => [
            ['$match' => ['name' => 'Pankaj Choudhary']],

            ['$unwind'=>'$skills'],
            ['$lookup' => array('from'=>'mytestskills','localField'=>'skills','foreignField'=>'_id','as'=>'sdfg')],
            ['$unwind'=>'$sdfg'],

            ['$group'=>array('_id'=>array('_id'=>'$_id','name'=>'$name','email'=>'$email'),'skills'=>array('$push'=>'$skills'),'sdfg'=>array('$push'=>'$sdfg'))],


        ],
        'cursor' => new \stdClass,
    ]);
    $cursor = $m->executeCommand('targetjob-plus', $command);
    $result = $cursor->toArray();
Pankaj Cheema
la source
définissez votre table d'entrée s'il vous plaît d'abord
Pankaj Cheema