21

In the following query:

db.orders.aggregate([{ $match : { status: "A"}, { $limit: 5} }]);

How can I get the count of documents after the $match but before the $limit is applied?

I still want to return an array of 5 documents. But if I use $group it seems that it would not preserve the array of documents. Can this be done in one call, or do I have to make two calls?

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
zenibeat
  • 212
  • 1
  • 2
  • 6

4 Answers4

30

Starting in Mongo 3.4, the $facet aggregation stage allows processing multiple aggregation pipelines within a single stage on the same set of input documents:

// { "status" : "A", "v" : 3 }
// { "status" : "B", "v" : 14 }
// { "status" : "A", "v" : 7 }
// { "status" : "A", "v" : 5 }
db.collection.aggregate(
  { $match: { status: "A"} },
  { $facet: {
      count:  [{ $count: "count" }],
      sample: [{ $limit: 2 }]
  }}
)
// {
//   "count" : [ { "count" : 3 } ],
//   "sample" : [ { "status" : "A", "v" : 3 }, { "status" : "A", "v" : 7 } ]
// }

This:

  • starts by matching documents whose status is A.

  • and then produces via a $facet stage two fields that out of two distinct aggregation pipelines:

    • $count that simply provides the number of documents resulting from the preceding $match stage.

    • sample that is a $limited extract of documents resulting from the preceding $match stage.

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
  • I am prohibited from doing this because my pipeline uses a mongoDB Atlas search stage – nrmad Jul 09 '21 at 09:51
  • 1
    The issue with using `$facet` is it returns _a single document_. Although it contains a list of documents, this document itself, like other documents in mongodb, is limited to 16MB. The `$limit` can be used to limit its size, though a single document in the response may violate it. Therefore, if you suspect the result may exceed 16MB, the best way is to query twice. – am.rez Jan 29 '22 at 12:04
4

Unfortunately, right now you have to make two calls, one call with the $limit operator for your results followed by a second call for the count. You could use the aggregation framework without the $limit operator and with a $group operator to calculate a count or as wdberkeley points out you can pass your criteria to .count() to get a count instead of using the aggregation framework if you are using a single match stage.

See MongoDB - Aggregation Framework (Total Count).

Community
  • 1
  • 1
Brantino
  • 544
  • 1
  • 5
  • 15
  • 1
    Use [.count()](http://docs.mongodb.org/manual/reference/method/db.collection.count/) to count the total number of results. – wdberkeley Sep 18 '14 at 19:14
  • 1
    @wdberkeley with aggregate it doesn't work. You have to make two calls, like Brantino said – 1nstinct Jun 11 '15 at 11:45
  • 1
    Beware that if you use `.count()`, then duplicate results will be counted that might have been eliminated by $group in your aggregate call. – cib Jun 28 '17 at 11:53
4

This can now be done in one call as of version 3.4 with the $facet aggregation. See here for an example: Mongo aggregation with paginated data and totals

TylerFowler
  • 568
  • 4
  • 8
-2

Another option is to limit the results at the application layer instead of in the Mongo query. Then you can get a count of the full result and you don't have to make two calls.

For example, in Python you would do:

data = db.orders.aggregate([{ "$match" : { "status": "A"} }])['result']
count = len(data)
data = data[skip:skip+limit]

If you have a complicated aggregate call on a large dataset, this is significantly faster than making two calls.

  • 3
    If the dataset is very large it might not fit in memory - that's why you can get a streaming cursor instead of loading all the result set in the application server memory. Moreover, executing to counting operation by the db server would save time for your application server to do something else. – Romain G Oct 15 '15 at 13:38