suryanaga
suryanaga

Reputation: 4022

Count subdocuments for each document

I'm trying to just count subdocuments in Mongo for each document.

It's easy to get documents in a collection with db.users.find().length(). I want to do something similar like db.users.projects.find().length(). How can I do this?

Edit:

The result I want is to know how many projects each user has... So something like:

{_id: 123, projects: 4}

Example user document:

{
   _id:{$oid: 123},
   username: johnsmith,
   projects: [{$oid: 456}, {$oid: 789}]
}

Upvotes: 13

Views: 12391

Answers (4)

amitsin6h
amitsin6h

Reputation: 193

A better approach for counting sub-document using mongoose using VirtualType() Let us understand this by a simple example using Blogpost and Comment schema design

const Blogpost = new Schema({
    title: { type: String, required: true },
    body: { type: String, required: true },
    userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
    commentId: [{ type: Schema.Types.ObjectId, ref: 'Comment' }],
    lastModifiedAt: { type: Date, default: Date.now },
    createdAt: { type: Date, default: Date.now },
})


const Comment = new Schema({
    postId: { type: Schema.Types.ObjectId, ref: 'Post', required: true },
    userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
    text: { type: String, required: true },
    lastModified: { type: Date, default: Date.now },
    createdAt: { type: Date, default: Date.now }
})

Now, if we have to get the totalComments for every blog post then we have to create a VirtualType in Blogpost schema design

Blogpost.virtual('totalComments', {
    ref: 'Comment', // model to use for matching
    localField: '_id', // from `localField` i.e., Blogpost
    foreignField: 'postId', // is equal to `foreignField` of Comment schema
    count: true //only get the number of docs
});

Blogpost.set('toObject', { virtuals: true });
Blogpost.set('toJSON', { virtuals: true });

As we have postId in Comment schema that holds the Blogpost._id which is associated with that particular Blogpost then only we are able to match and get the totalComments for every Blogpost

Now using population we can get the totalComments for every Blogpost

let blogpost = await Blogpost.model
    .find({})
    .populate({ path: 'totalComments', count: true })

Upvotes: 0

Buzz Moschetti
Buzz Moschetti

Reputation: 7621

It seems like you may have multiple username docs in your design, e.g.

{ username: "buzz", projects: [ list of 2 things ] }
{ username: "dan". projects: [ list of 3 things ] }
{ username: "buzz", projects: [ list of 4 things ] }

To arrive at the "grant total" of projects for each username try this:

c = db.foo.aggregate([
{$project: {
    "username": 1,
    "n": {$size: "$projects"}
}
}
, 
{$group: {
    "_id": "$username",
    "alln": {$sum: "$n"}
}
}
]);

to yield

{ "_id" : "buzz", "alln" : 6 }
{ "_id" : "dan", "alln" : 3 }

Upvotes: 1

EmptyArsenal
EmptyArsenal

Reputation: 7464

Per @n9code, you will need the Aggregation Framework. However, you can very easily count the sub-documents with $size:

db.users.aggregate([{
  $project: {
    _id: '$_id',
    totalProjects: { $size: "$projects" }
  }
}]);

Which should return something like this:

{ "_id" : ObjectID(...), "totalProjects" : 89 }, ...

$size will return the length of the projects array for each document and $project changes the format of the documents to include totalProjects as the size of the projects array.

Upvotes: 13

bagrat
bagrat

Reputation: 7458

You need MongoDB's Aggregation Framework here:

db.users.aggregate({$unwind: '$projects'}).count()

This will unwind the projects array field in each document of your collection, which will be the total number of projects subdocuments.

Upvotes: -1

Related Questions