16

In Mongoose, I need to find elements in a collection and count them, and getting both the results of the find and count. I have tried

Model.find().count(function (err, count) {
    // Get count, but cannot get results of find
});

Is there a way to get both find() and count() without calling them twice?

Marc Bacvanski
  • 1,458
  • 4
  • 17
  • 33

6 Answers6

25

You can use the length of the returned array:

Model.find().exec(function (err, results) {
  var count = results.length

});
Gergo
  • 2,190
  • 22
  • 24
  • 1
    What is the purpose of .exec here? – Marc Bacvanski Feb 16 '16 at 22:04
  • It is just a coding style, you can write it like this: `Model.find({}, function(err, results) {})`, check this: http://mongoosejs.com/docs/api.html#model_Model.find – Gergo Feb 16 '16 at 22:07
  • 1
    @MarcB Though it's not the best explanation, this is technically correct, and far more efficient than the answer you have currently accepted. It's an artifact of the way mongoose handles the resulting cursor by "automagically" converting the result into an "array". Therefore the logical thing to do is return the array "length" as a "count". This avoids doing it twice. Only if you wanted to use the "cursor" directly as a "stream" would it be neccessary to run a separate count. But this is arguably handled in stream processing as well. – Blakes Seven Feb 16 '16 at 23:36
  • @MarcB `var count = 0; var stream = Model.find().stream(); stream.on("data", function(data) { count++; // other stuff }); stream.end("end",function(){ console.log(count) });` In short. – Blakes Seven Feb 16 '16 at 23:37
  • @BlakesSeven You can check arr.length everywhere and i can't really imagine that's what the OP meant (considering he used the `count` method). you usually want to `count` the number of all entries matching your query in the DB and `find` a set of those. This solution doesn't even need to answer separately with count, you can always use the length property of an array. – user1695032 Feb 17 '16 at 08:06
  • Although i can't argue, this is a lot easier and makes more sense if you know you will not have any limit constraints. I would still say that it's not a good practice. It might create some "gotchas" later. – user1695032 Feb 17 '16 at 09:17
  • @user1695032 I think you might not have really been paying attention to "mongoose" and the usage without streams as I explained. It "does work everywhere" because that is just what mongoose does. So it's taking advantage of the "convenience" that is built in by the array conversion, rather than a "general solution" applied to all drivers for MongoDB. Mongoose is a "higher level" abstraction by intent. So people really should use it that way when using that call syntax. That's why I give "props" to the post here for basically stating that. – Blakes Seven Feb 17 '16 at 09:45
  • 1
    @BlakesSeven Sorry, i was actually commenting on the original answer. I guess it really comes down to how you wish to use the 'count'. I made a couple of assumptions here. I thought about practical implementations in a webapp context and why you would need a count at all. This might be something you wish to show the user: 'showing 32{results.length} results:' followed by a list of results. But what if the dataset reaches thousands of entries. You would need something like this: 'showing 50 results out of 4331 {results.length === 50}'. – user1695032 Feb 17 '16 at 10:04
  • 1
    @user1695032 True, but the point was that "big response" was out of context for a call syntax that would result in an array. It wasn't the answer given, but I did add the "stream" context for "big results" in comment (to add that value). And yes you can also "stream" JSON writer output as well. So counts in large results are valid without using `.count()` as an additional call. It's really only needed when "paging". When of course you should **always** use two queries. One for full count, and the other for the current page. – Blakes Seven Feb 17 '16 at 11:05
  • Yes, it seems that we approached the question from two really different angles. Why i wasn't satisfied with that answer is because if someone googles this issue and finds this answer without reading the comments, they will probably get the wrong idea. I might be overthinking this but right now googling led me to this kind of answer (although the question more specific to the answer) http://stackoverflow.com/questions/13935733/mongoose-limit-offset-and-count-query . My assumption was that this was the actual question (which might have been wrong) – user1695032 Feb 17 '16 at 11:28
  • So there's no way to use the native module? That should be mentioned in the answer – Anthony Sep 19 '17 at 20:28
12

You have to do 2 separate queries unfortunately. Festo's answer only works if you have less elements in the database than the limit.

var countQuery = Model.count();
var findQuery = Model.find().limit(2);

countQuery.exec(function (e, count) {
  console.log('count', count); // can be more than 2, this is not calculated, mongo stores this value internally
})
findQuery.exec(function(e, data) {
  console.log('found items', data); // will be 2 or less elements
});
user1695032
  • 1,112
  • 6
  • 10
  • 1
    Could you clarify what you mean by the limit? – Marc Bacvanski Feb 16 '16 at 22:50
  • Well imagine if you have 1000 entries in your database. You don't want to get them all at once. I think mongoose has some kind of default limit set as well. You want to know the count (1000 in this case) but return only maybe 50 results. If you try to count the length of the response, you will get 50. – user1695032 Feb 16 '16 at 22:52
  • Also, you will use limit, if you want to show results to the user and do pagination. So if every page has 50 responses, your third page results might be queried like this: `query.skip(100).limit(50)` you skip the first 2x50 and show the third batch of 50 results. – user1695032 Feb 16 '16 at 22:59
5

As stated in the mongoose documentation and in the answer by Benjamin, the method Model.count() is deprecated. Instead of using count(), the alternatives are the following:

  SomeModel.countDocuments({}, function(err, count) {
    if (err) { return handleError(err) } //handle possible errors
    console.log(count)
    //and do some other fancy stuff
})

or

SomeModel
.estimatedDocumentCount()
.then(count => {
    console.log(count)
    //and do one super neat trick
})
.catch(err => {
    //handle possible errors
})
SirPhemmiey
  • 585
  • 6
  • 7
3

You can also use mongoose-paginate plugin.

For example:

Model.paginate({}, { offset: 100, limit: 0 }).then(function(result) {
      // result.docs - Array of documents
      // result.total - Total number of documents in collection that match a query
      // result.limit - 0
      // result.offset - 100
});
Pulkit chadha
  • 816
  • 11
  • 14
0

DeprecationWarning: collection.count is deprecated, and will be removed in a future version. Use Collection.countDocuments or Collection.estimatedDocumentCount instead. Hope this update helps someone. Example :

var user = await User.find().countDocuments()

Athira V Ajit
  • 316
  • 5
  • 8
0

Just a better way to write

try{
   let result = await Model.find();
   console.log(result); //result
   console.log(result.length); //count
} catch(err){
   //error
}
Sensei_75
  • 56
  • 4