Mongo-Gruppenabfrage, wie Felder gehalten werden

94

Jeder. In der Mongo-Gruppenabfrage zeigt das Ergebnis nur die Schlüssel in Argumenten. So behalten Sie das erste Dokument in jeder Gruppe wie eine MySQL-Abfragegruppe. beispielsweise:

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

Ergebnis:

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

So erhalten Sie Folgendes:

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

Antworten:

212

Wenn Sie die Informationen zu den ersten übereinstimmenden Einträgen für jede Gruppe behalten möchten, können Sie versuchen, wie folgt zu aggregieren:

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
quelle
2
Warum brauchst du {$first: '$age'}etc.? Ist es möglich, nur zu haben age: $age?
Lichtchemiker
5
@lightalchemist Es ist nicht möglich. Es ist eine Art Trick, 'Gruppe' wissen zu lassen, was sie wählen soll.
TechWisdom
4
Was wäre, wenn diese Aggregation anstelle der Zählung ein $ max oder $ min für das Alter ergibt? Das $ first würde nicht unbedingt mit dem Mindest- oder Höchstalter übereinstimmen, das für die anderen Felder gefunden wurde. Wie gehe ich dann damit um?
Juliomac
2
Dies funktioniert nicht, es gruppiert sich nach den anderen Feldern, was unerwünscht ist.
Jack Cole
1
@Juliomac, ich glaube, wenn Ihre gewünschte Ausgabe $ max / $ min ist und Felder $groupbeibehalten, die nicht in der _id enthalten sind, können Sie $sortvorher mit dem gewünschten Feld gruppieren und verwenden $firstoder $lastOperatoren für ein beliebiges Feld. Beim Akkumulieren macht die Idee, andere Felder (die akkumuliert / geleitet / reduziert werden) einzubeziehen, auch theoretisch nicht so viel Sinn. Das Sortieren vor der Hand ist jedoch im Vergleich zum Sortieren jeder Gruppe in sich selbst ineffizient, da Sortieralgorithmen komplexer sind als O (n). Ich wünschte, es gäbe bessere Möglichkeiten in MongoDB.
Vemulo
16

Übrigens, wenn Sie nicht nur das erste Dokument behalten möchten, können Sie $ addToSet verwenden . Beispiel:

db.test.aggregate({
  $group: {
    _id: '$name',
    name : { $addToSet: '$name' }
    age : { $addToSet: '$age' },
    count: { $sum: 1 }
  }
}
陈 学 铄
quelle
1
Vielen Dank! Habe es besser gemacht (vermeide es, die Bestellung mit einem Set zu verwechseln): data: {$ addToSet: {name: '$ name', _id: '$ _id', age: '$ age'}}
Benoit
12

Ich bin hierher gekommen, um eine Antwort zu suchen, war aber mit der ausgewählten Antwort nicht zufrieden (besonders angesichts des Alters). Ich fand diese Antwort , die eine bessere Lösung ist (angepasst):

db.test.aggregate({
  $group: {
    _id: '$name',
   person: { "$first": "$$ROOT" },
   count: { $sum: 1 }
  },
  {
    "$replaceRoot": { "newRoot": "$person" }
  }
}
Pieter
quelle
2
ABER du verlierst das countFeld. Sie müssen es verwenden $mergeObjects, um es zu behalten.
0zkr PM
7

Sie können dies ausprobieren

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

}
Deeksha Sharma
quelle
4

Das habe ich getan, es funktioniert gut.

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

Nur ein kurzes Update, wenn bei Dokumenten mit zahlreichen Feldern das gleiche Problem auftritt. Man kann die Kraft der Kombination der $replaceRootPipeline-Stufe und des $mergeObjectsPipeline-Betreibers nutzen.

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

Ich wusste nichts über .groupHelfer, aber wenn Sie es vorziehen, mit dem Aggregation Framework zu arbeiten , müssen Sie angeben, welche Felder zurückgegeben werden sollen. Korrigieren Sie mich, wenn ich falsch liege, aber in SQL müssten Sie das trotzdem tun.

Nun, so würden Sie es mit dem zuvor erwähnten Aggregation Framework machen:

db.test.aggregate({
  $group: {
    _id: { name: "$name", city: "$city", fieldName: "$fieldName" },
    count: { $sum: 1 }
  }
})
gustavohenke
quelle
10
Danke für deine Hilfe. In dieser Abfrage sind gruppenspezifische Felder, ich möchte nur nach einem Feld gruppieren und dann andere Felder angeben. Irgendeine gute Idee?
plus oder
1

Ich habe diese Funktion erstellt, um das Umkehren einer Abwicklungsphase zu verallgemeinern ... lass es mich wissen, wenn ihr auf irgendwelche Fehler stößt, aber es funktioniert gut für mich!

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
quelle
Am besten, wenn Dokumente in derselben Sammlung unterschiedliche Feldnamen haben.
user7364588
0

Mit $firstdem $$ROOTDokument verwenden und dann $replaceRootmit dem ersten Feld verwenden.

db.test.aggregate([
  { "$group": {
    "_id": "$name",
    "doc": { "$first": "$$ROOT" }
  }},
  { "$replaceRoot": { "newRoot": "$doc" }}
])
Ashh
quelle
0

Wenn Sie alle Felder projizieren möchten, dokumentieren Sie diese Abfrage unten.

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

Hier ist die Antwort >>>>

    $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
quelle
Stellen Sie bitte zuerst Ihre Eingabetabelle ein
Pankaj Cheema