Search code examples
node.jsmongodbmongoosedocument-databaseodm

Nesting and querying documents with MongoDB


I have a data structure that looks more or less like this:

var city = {
  name: String,
  mayor: Person,
  citizens: [Person]
};

I think my use case is pretty good for using MongoDB, but I have a few questions. The above model has been implemented with mongoose, and I use sub documents to nest Persons inside City.Obviously the citizens array could get quite long, and that's why MongoDB seems like a good choice.

Is this an efficient way to structure my data? I'm wondering if Mongo will have to do some sort join each time I want to select a city, with all of it's citizens (or a large part of them). That would obviously defeat the purpose of using a document database.

Also, when in the mongo terminal i try something like db.cities.find({name:'Berlin'}).mayor I don't get any results. When I try db.cities.find({name:'Berlin'}) it shows the city, and it also shows an object id for the mayor but not all the properties of the mayer/Person.

So how do I query with sub documents and is this a good way of working?


Solution

  • I would recommend your schema as following with Populate. There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in.

    var personSchema = Schema({
      _id     : Number,
      name    : String,
    }); 
    
    var Person = mongoose.model('Person', personSchema);
    
    var citySchema = Schema{
      name: String,
      mayor: { type: Schema.Types.ObjectId, ref: 'Person' },
      citizens: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
    };
    
    var City = mongoose.model('City', citySchema);
    

    To query the city like Berlin through

    City.find({name: 'Berlin'})
        .populate('mayor')
        .populate('citizens')
        .exec(...)
    

    The mayor and citizens related document will be retrieved from DB.

    If you concern there are too many citizens in the city to make the city too large with populate the person in the city, another option is

    var personSchema = Schema({
      _id     : Number,
      name    : String,
      cityId  : { type: Schema.Types.ObjectId, ref: 'City' }
    }); 
    
    var Person = mongoose.model('Person', personSchema);
    
    var citySchema = Schema{
      name: String,
      mayor: { type: Schema.Types.ObjectId, ref: 'Person' }
    };
    
    var City = mongoose.model('City', citySchema);
    

    To query the city Berlin with aggregation, here are the sample codes.

    City.find({name: 'Berlin'})
        .populate('mayor')
        .exec(function(err, city) {
           Person.aggregate([
              {$group: {cityId: city._id, citizens: {$push: '$name'}}}
              // ...
           ], function(err, result) {
              //...
    });