mongo group query como manter os campos

100

Todo mundo. Na consulta de grupo mongo, o resultado mostra apenas a (s) chave (s) nos argumentos. Como manter o primeiro documento em cada grupo, como o grupo de consulta mysql. por exemplo:

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

resultado:

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

Como obter o seguinte:

[
   {
   "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
fonte

Respostas:

221

Se quiser manter as informações sobre as primeiras entradas correspondentes para cada grupo, você pode tentar agregar como:

    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
fonte
4
Por que você precisa {$first: '$age'}etc.? É possível apenas ter age: $age?
lightalchemist
7
@lightalchemist Não é possível. É uma espécie de truque para deixar o 'grupo' saber o que escolher.
TechWisdom
4
E se, em vez de contar, essa agregação estivesse fazendo $ max ou $ min para idade? O $ primeiro não corresponderia necessariamente à idade mínima ou máxima encontrada para os outros campos. Como lidar com isso então?
Juliomac 01 de
2
Isso não funciona, ele agrupa pelos outros campos o que é indesejado.
Jack Cole
1
@Juliomac, acredito que se sua saída desejada for $ max / $ min e mantendo os campos que não estão no $group_id, você pode $sortantes com o campo desejado agrupar e usar $firstou $lastoperadores em qualquer campo. Ao acumular, a ideia de incluir outros campos (que são acumulados / afunilados / reduzidos) não faz muito sentido, mesmo teoricamente. No entanto, a classificação antecipada é realmente ineficiente em comparação com a classificação de cada grupo dentro deles, uma vez que os algoritmos de classificação são mais complexos do que O (n). Eu gostaria que houvesse maneiras melhores no MongoDB.
Vemulo
16

A propósito, se você quiser manter não apenas o primeiro documento, pode usar $ addToSet. Por exemplo:

db.test.aggregate({
  $group: {
    _id: '$name',
    name : { $addToSet: '$name' }
    age : { $addToSet: '$age' },
    count: { $sum: 1 }
  }
}
陈 学 铄
fonte
1
Obrigado! Entendi (evite bagunçar o pedido com um conjunto): data: {$ addToSet: {name: '$ name', _id: '$ _id', age: '$ age'}}
Benoit
16

[editado para incluir sugestões de comentários]

Vim aqui à procura de uma resposta, mas não fiquei satisfeito com a resposta selecionada (especialmente devido à sua idade). Achei esta resposta que é uma solução melhor (adaptada):

db.test.aggregate({
  $group: {
    _id: '$name',
   person: { "$first": "$$ROOT" },
   count: { $sum: 1 }
  },
  {
    "$replaceRoot": { "newRoot": { "$mergeObjects": ["$person", { count: "$count" }]} }
  }
}
Pieter
fonte
3
MAS, você perde o countcampo. Você precisa usar $mergeObjectspara mantê-lo.
0zkr PM
1
Para elaborar o comentário de 0zkr sobre o uso de $ mergeObjects e ajudar outros com a sintaxe, a última sintaxe do pipeline seria{"$replaceRoot": {"newRoot": {"$mergeObjects": ["$person", {count: "$count"}]}}}
Jerren Saunders
7

Você pode experimentar isto

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

}
Deeksha Sharma
fonte
4

Isso é o que eu fiz, funciona bem.

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
fonte
4

Apenas uma atualização rápida se alguém enfrentar o mesmo problema com documentos com vários campos. Pode-se usar o poder de combinar o $replaceRootestágio do $mergeObjectsoleoduto e o operador do oleoduto.

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

Use $firstcom o $$ROOTdocumento e depois use $replaceRootcom o primeiro campo.

db.test.aggregate([
  { "$group": {
    "_id": "$name",
    "doc": { "$first": "$$ROOT" }
  }},
  { "$replaceRoot": { "newRoot": "$doc" }}
])
Ashh
fonte
Isso foi muito útil! Obrigado!! Eu estive procurando por um tempo, mas não consegui encontrar exatamente o que eu precisava. Isso foi perfeito!
The Student Soul
Esta resposta é 'objetiva' e perfeita. Obrigado!
M. Nunisa
1

Eu não sabia sobre o .grouphelper, mas se você preferir usar o Aggregation Framework , você terá que especificar quais campos retornar. Corrija-me se eu estiver errado, mas no SQL você teria que fazer isso de qualquer maneira.

Bem, é assim que você faria com a estrutura de agregação mencionada anteriormente:

db.test.aggregate({
  $group: {
    _id: { name: "$name", city: "$city", fieldName: "$fieldName" },
    count: { $sum: 1 }
  }
})
Gustavohenke
fonte
10
obrigado pela sua ajuda. nesta consulta são campos especificados de grupo, eu só quero agrupar por um campo e, em seguida, os outros campos especificam o resultado. alguma boa ideia?
plusor
1

Eu criei esta função para generalizar a reversão de um estágio de desenrolamento ... deixe-me saber se vocês encontraram algum bug com ela, mas está funcionando bem para mim!

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
fonte
Melhor quando os documentos na mesma coleção têm vários nomes de campo.
user7364588
0

Se você deseja projetar todos os campos do documento, use a consulta abaixo.

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

Aqui está a resposta >>>>

    $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
fonte
defina sua tabela de entrada primeiro
Pankaj Cheema