Find a Document based on a Reference To the Parent

2019-07-25 12:02发布

问题:

There are 2 collections Movie and Rank Movie .This 2 collections having referenced relationship .

Movie model

var mongoose = require('mongoose');

var movieSchema = new mongoose.Schema({
    m_tmdb_id: {
        type: Number,
        unique: true,
        index: true
    },
    m_adult: {
        type: Boolean
    },
    m_backdrop_path: {
        type: String,
    },
    m_title: {
        type: Number
    },
    m_genres: {
        type: Array
    }

});
var MovieModel = mongoose.model('Movie', movieSchema);
module.exports = {
    movie: MovieModel
}

Rank movie model

var mongoose = require('mongoose');
var rankMovieSchema = new mongoose.Schema({
    movie: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Movie',
        unique: true
    },
    rank: {
        type: Number
    },
    count: {
        type: Number
    }
});
var RankMovieModel = mongoose.model('RankMovie', rankMovieSchema);
module.exports = {
    rankmovie: RankMovieModel
}

I need to select all the items from the collection rank movie having a particular title[Condition on movie collection ].How can i achieve this?

回答1:

Actually the "best" way to do this is rather using .aggregate() and $lookup to "join" the data and "filter" on the match conditions. This is very effective since MongoDB actually performs all of this on the "server" itself, as compared to issuing "multiple" queries as .populate() does.

MovieModel.aggregate([
  { "$match": { "m_title": m_title } },
  { "$lookup": {
    "from": RankMovieModel.collection.name,
    "localField": "_id",
    "foreignField": "movie",
    "as": "rankings"
  }}
])

Note: The RankMovieModel.collection.name is a nice way of getting the "underlying" collection name from the Model registered with Mongoose. Since the operation is on the "server", MongoDB needs the "real collection name", so we can either "hardcode" that or just get it from the information registered on the Model itself. As is done here.

If there are "lots" of rankings, then you are best to use $unwind, which will create a document for each related "rankings" item:

MovieModel.aggregate([
  { "$match": { "m_title": m_title } },
  { "$lookup": {
    "from": RankMovieModel.collection.name,
    "localField": "_id",
    "foreignField": "movie",
    "as": "rankings"
  }},
  { "$unwind": "$rankings" }
])

There is also a special handling here of how MongoDB deals with "joining" documents to avoid breaching the 16MB BSON limit. So in fact this special thing happens when $unwind directly follows a $lookup pipeline stage:

    {
        "$lookup" : {
            "from" : "rankmovies",
            "as" : "rankings",
            "localField" : "_id",
            "foreignField" : "movie",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            }
        }
    }

So the $unwind actually "disappears" and instead is "rolled-up" into the $lookup itself as if this were "one" operation. That way we do not create an "array" directly within the parent document which would cause the size to exceed 16MB in extreme cases with many "related" items.


If you do not have a MongoDB that supports $lookup ( MongoDB 3.2 minunum ) then you could use a "virtual" with .populate() instead (requires Mongoose 4.5.0 minimum). But note this actually performs "two" queries to the server:

First add the "virtual" to the schema:

movieSchema.virtual("rankings",{
  "ref": "Movie",
  "localField": "_id",
  "foreignField": "movie"
});

Then issue the query with .populate():

MovieModel.find({ "m_title": m_title })
  .populate('rankings')
  .exec()