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

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

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

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

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

}

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

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

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

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
}

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

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

-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();

Stellen Sie bitte zuerst Ihre Eingabetabelle ein
Pankaj Cheema
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.