MongoDB-Aggregation: Wie erhalte ich die Gesamtzahl der Datensätze?

97

Ich habe die Aggregation zum Abrufen von Datensätzen aus Mongodb verwendet.

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),
  array('$skip' => $skip),
  array('$limit' => $limit),
));

Wenn ich diese Abfrage unbegrenzt ausführe, werden 10 Datensätze abgerufen. Aber ich möchte das Limit auf 2 halten. Also möchte ich die Gesamtzahl der Datensätze erhalten. Wie kann ich mit Aggregation umgehen? Bitte berate mich. Vielen Dank

user2987836
quelle
Wie würden die Ergebnisse aussehen, wenn es nur 2 gäbe?
WiredPrairie
Werfen
Soham

Antworten:

99

Dies ist eine der am häufigsten gestellten Fragen, um das paginierte Ergebnis und die Gesamtzahl der Ergebnisse gleichzeitig in einer einzelnen Abfrage zu erhalten. Ich kann nicht erklären, wie ich mich gefühlt habe, als ich es endlich erreicht habe. LOL.

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),

// get total, AND preserve the results
  array('$group' => array('_id' => null, 'total' => array( '$sum' => 1 ), 'results' => array( '$push' => '$$ROOT' ) ),
// apply limit and offset
  array('$project' => array( 'total' => 1, 'results' => array( '$slice' => array( '$results', $skip, $length ) ) ) )
))

Das Ergebnis sieht ungefähr so ​​aus:

[
  {
    "_id": null,
    "total": ...,
    "results": [
      {...},
      {...},
      {...},
    ]
  }
]
Anurag Pareek
quelle
8
Dokumentation dazu: docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... Beachten Sie, dass bei diesem Ansatz die gesamte nicht paginierte Ergebnismenge in 16 MB passen muss.
Btown
7
Das ist reines Gold! Ich ging durch die Hölle und versuchte, diese Arbeit zu machen.
Henrique Miranda
4
Danke Kerl! Ich brauche { $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(füge nach ein, {$group:{}}um die Gesamtzahl zu finden.
Liberateur
1
Wie können Sie die Ergebnismenge begrenzen? Ergebnisse ist jetzt ein verschachteltes Array
valen
@valen Sie können die letzte Codezeile sehen "" results '=> array (' $ Slice '=> array (' $ results ', $ skip, $ length)) "Hier können Sie Limit- und Skip-Parameter anwenden
Anurag pareek
79

Seit v.3.4 (glaube ich) hat MongoDB jetzt einen neuen Aggregationspipeline-Operator namens ' facet ', der in eigenen Worten:

Verarbeitet mehrere Aggregations-Pipelines in einer einzigen Phase auf demselben Satz von Eingabedokumenten. Jede Subpipeline verfügt über ein eigenes Feld im Ausgabedokument, in dem die Ergebnisse als Array von Dokumenten gespeichert werden.

In diesem speziellen Fall bedeutet dies, dass man so etwas tun kann:

$result = $collection->aggregate([
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  $facet: {
    paginatedResults: [{ $skip: skipPage }, { $limit: perPage }],
    totalCount: [
      {
        $count: 'count'
      }
    ]
  }
]);

Das Ergebnis ist (mit beispielsweise 100 Gesamtergebnissen):

[
  {
    "paginatedResults":[{...},{...},{...}, ...],
    "totalCount":[{"count":100}]
  }
]
user3658510
quelle
13
Dies funktioniert hervorragend, ab 3.4 sollte dies die akzeptierte Antwort sein
Adam Reis
Um so ein Array-Ergebnis in ein einfaches Zwei-Feld-Objekt umzuwandeln, brauche ich ein anderes $project?
SerG
1
Dies muss nun die akzeptierte Antwort sein. arbeitete wie Charme.
Arootin Aghazaryan
8
Dies sollte heute die akzeptierte Antwort sein. Bei der Verwendung von Paging mit $ facet wurden jedoch Leistungsprobleme festgestellt. Die andere Antwort hat ebenfalls Leistungsprobleme mit $ Slice. Ich fand es besser, $ überspringen und $ begrenzen in der Pipeline und einen separaten Aufruf zur Zählung zu tätigen. Ich habe dies gegen ziemlich große Datenmengen getestet.
Jpepper
57

Verwenden Sie diese Option, um die Gesamtzahl in der resultierenden Sammlung zu ermitteln.

db.collection.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
Vishal Ranapariya
quelle
3
Vielen Dank. Ich habe jedoch "Ansichten" in meiner Codierung verwendet, um die Anzahl der entsprechenden Gruppen zu ermitteln (dh Gruppe 1 => 2 Datensätze, Gruppe 3 => 5 Datensätze usw.). Ich möchte die Anzahl der Datensätze erhalten (dh insgesamt: 120 Datensätze). Hoffe du hast verstanden ..
user2987836
32

Sie können die toArray-Funktion verwenden und dann ihre Länge für die Gesamtzahl der Datensätze abrufen.

db.CollectionName.aggregate([....]).toArray().length
Ankit Arya
quelle
1
Obwohl dies möglicherweise nicht als "richtige" Lösung funktioniert, hat es mir beim Debuggen geholfen - es funktioniert, auch wenn es keine 100% ige Lösung ist.
Johann Marx
3
Dies ist keine echte Lösung.
Furkan Başaran
1
TypeError: Parent.aggregate(...).toArray is not a functionDies ist der Fehler, den ich bei dieser Lösung gegeben habe.
Mohammad Hossein Shojaeinia
Vielen Dank. Das habe ich gesucht.
skvp
Dadurch werden alle aggregierten Daten abgerufen und die Länge dieses Arrays zurückgegeben. keine gute Praxis. Stattdessen können Sie {$ count: 'count'} in die Aggregationspipeline einfügen
Aslam Shaik
18

Verwenden Sie die Pipeline-Phase $ count Aggregation , um die Gesamtanzahl der Dokumente abzurufen:

Abfrage:

db.collection.aggregate(
  [
    {
      $match: {
        ...
      }
    },
    {
      $group: {
        ...
      }
    },
    {
      $count: "totalCount"
    }
  ]
)

Ergebnis:

{
   "totalCount" : Number of records (some integer value)
}
cnsnaveen
quelle
Das funktioniert wie ein Zauber, aber was die Leistung betrifft, ist es gut?
Ana.arede
Saubere Lösung. Danke
skvp
13

Ich habe es so gemacht:

db.collection.aggregate([
     { $match : { score : { $gt : 70, $lte : 90 } } },
     { $group: { _id: null, count: { $sum: 1 } } }
] ).map(function(record, index){
        print(index);
 });

Das Aggregat gibt das Array zurück, also schleifen Sie es einfach und erhalten Sie den endgültigen Index.

Und eine andere Möglichkeit ist:

var count = 0 ;
db.collection.aggregate([
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] ).map(function(record, index){
        count++
 }); 
print(count);
verrückter Mann
quelle
fwiw Sie brauchen weder die varErklärung noch den mapAnruf. Die ersten 3 Zeilen Ihres ersten Beispiels sind ausreichend.
Madbreaks
6

Die von @Divergent bereitgestellte Lösung funktioniert, aber meiner Erfahrung nach ist es besser, zwei Abfragen zu haben:

  1. Zuerst zum Filtern und dann zum Gruppieren nach ID, um die Anzahl der gefilterten Elemente zu erhalten. Filtern Sie hier nicht, es ist unnötig.
  2. Zweite Abfrage, die filtert, sortiert und paginiert.

Die Lösung durch Drücken von $$ ROOT und Verwenden von $ Slice führt zu einer Dokumentenspeicherbeschränkung von 16 MB für große Sammlungen. Bei großen Sammlungen scheinen zwei Abfragen zusammen schneller zu laufen als die mit $$ ROOT Pushing. Sie können sie auch parallel ausführen, sodass Sie nur durch die langsamere der beiden Abfragen (wahrscheinlich die sortierte) eingeschränkt sind.

Ich habe mich mit dieser Lösung unter Verwendung von 2 Abfragen und einem Aggregationsframework abgefunden (Hinweis - in diesem Beispiel verwende ich node.js, aber die Idee ist dieselbe):

var aggregation = [
  {
    // If you can match fields at the begining, match as many as early as possible.
    $match: {...}
  },
  {
    // Projection.
    $project: {...}
  },
  {
    // Some things you can match only after projection or grouping, so do it now.
    $match: {...}
  }
];


// Copy filtering elements from the pipeline - this is the same for both counting number of fileter elements and for pagination queries.
var aggregationPaginated = aggregation.slice(0);

// Count filtered elements.
aggregation.push(
  {
    $group: {
      _id: null,
      count: { $sum: 1 }
    }
  }
);

// Sort in pagination query.
aggregationPaginated.push(
  {
    $sort: sorting
  }
);

// Paginate.
aggregationPaginated.push(
  {
    $limit: skip + length
  },
  {
    $skip: skip
  }
);

// I use mongoose.

// Get total count.
model.count(function(errCount, totalCount) {
  // Count filtered.
  model.aggregate(aggregation)
  .allowDiskUse(true)
  .exec(
  function(errFind, documents) {
    if (errFind) {
      // Errors.
      res.status(503);
      return res.json({
        'success': false,
        'response': 'err_counting'
      });
    }
    else {
      // Number of filtered elements.
      var numFiltered = documents[0].count;

      // Filter, sort and pagiante.
      model.request.aggregate(aggregationPaginated)
      .allowDiskUse(true)
      .exec(
        function(errFindP, documentsP) {
          if (errFindP) {
            // Errors.
            res.status(503);
            return res.json({
              'success': false,
              'response': 'err_pagination'
            });
          }
          else {
            return res.json({
              'success': true,
              'recordsTotal': totalCount,
              'recordsFiltered': numFiltered,
              'response': documentsP
            });
          }
      });
    }
  });
});
Filip Voska
quelle
5
//const total_count = await User.find(query).countDocuments();
//const users = await User.find(query).skip(+offset).limit(+limit).sort({[sort]: order}).select('-password');
const result = await User.aggregate([
  {$match : query},
  {$sort: {[sort]:order}},
  {$project: {password: 0, avatarData: 0, tokens: 0}},
  {$facet:{
      users: [{ $skip: +offset }, { $limit: +limit}],
      totalCount: [
        {
          $count: 'count'
        }
      ]
    }}
  ]);
console.log(JSON.stringify(result));
console.log(result[0]);
return res.status(200).json({users: result[0].users, total_count: result[0].totalCount[0].count});
Harpal Singh
quelle
1
Es wird normalerweise empfohlen, erklärenden Text zusammen mit einer Code-Antwort beizufügen.
3

Dies kann für mehrere Spielbedingungen funktionieren

            const query = [
                {
                    $facet: {
                    cancelled: [
                        { $match: { orderStatus: 'Cancelled' } },
                        { $count: 'cancelled' }
                    ],
                    pending: [
                        { $match: { orderStatus: 'Pending' } },
                        { $count: 'pending' }
                    ],
                    total: [
                        { $match: { isActive: true } },
                        { $count: 'total' }
                    ]
                    }
                },
                {
                    $project: {
                    cancelled: { $arrayElemAt: ['$cancelled.cancelled', 0] },
                    pending: { $arrayElemAt: ['$pending.pending', 0] },
                    total: { $arrayElemAt: ['$total.total', 0] }
                    }
                }
                ]
                Order.aggregate(query, (error, findRes) => {})
Rohit Parte
quelle
2

Ich brauchte die absolute Gesamtzahl nach dem Anwenden der Aggregation. Das hat bei mir funktioniert:

db.mycollection.aggregate([
    {
        $group: { 
            _id: { field1: "$field1", field2: "$field2" },
        }
    },
    { 
        $group: { 
            _id: null, count: { $sum: 1 } 
        } 
    }
])

Ergebnis:

{
    "_id" : null,
    "count" : 57.0
}
miqrc
quelle
2

Hier sind einige Möglichkeiten, um die Gesamtzahl der Datensätze während der MongoDB-Aggregation zu ermitteln:


  • Verwenden von $count:

    db.collection.aggregate([
       // Other stages here
       { $count: "Total" }
    ])

    Für 1000 Datensätze dauert dies durchschnittlich 2 ms und ist der schnellste Weg.


  • Verwenden von .toArray():

    db.collection.aggregate([...]).toArray().length

    Für 1000 Datensätze dauert dies durchschnittlich 18 ms.


  • Verwenden von .itcount():

    db.collection.aggregate([...]).itcount()

    Für 1000 Datensätze dauert dies durchschnittlich 14 ms.

palaSн
quelle
0

Entschuldigung, aber ich denke, Sie brauchen zwei Fragen. Eine für Gesamtansichten und eine für gruppierte Datensätze.

Sie können diese Antwort nützlich finden

Rubenfa
quelle
Danke. Ich denke schon. Aber es gibt keine Option mit Aggregation. :(
user2987836
1
Ich bin in eine ähnliche Situation geraten. Es gab keine andere Antwort, als 2 Abfragen durchzuführen. :( stackoverflow.com/questions/20113731/…
Astroanu
0

Wenn Sie nicht gruppieren möchten, verwenden Sie die folgende Methode:

db.collection.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $count: 'count' } ] );

Rajan Sharma
quelle
Ich denke, die Person, die die Frage stellt, möchte sich nach dem Thema gruppieren.
Mjaggard