根据分页子项中对父项的引用来查找文档

时间:2017-07-05 06:49:52

标签: node.js mongodb mongoose mongodb-query aggregation-framework

有2个系列电影排名电影。这2个系列引用关系

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
}

我需要从具有特定m_genres名称[电影收集条件]的集合排名电影中选择每个查询[分页]中的所有前10个项目。

例如:

电影收藏

[{
    "_id": ObjectId("5930ea4806fb9e365e0d55b5"),
    "m_tmdb_id": 888724,
    "m_imdb_id": "sd2806",
    "m_title": "Mechanic: Resurrection",
    "m_genres": [{
        "name": "Action",
        "id": 28
    }, {
        "name": "Crime",
        "id": 80
    }, {
        "name": "Thriller",
        "id": 53
    }]
}, {
    "_id": ObjectId("583e3bd69e2ea90c69c5a254"),
    "m_tmdb_id": 23924,
    "m_imdb_id": "tt3d2806",
    "m_title": "Inter",
    "m_genres": [{
        "name": "Drama",
        "id": 32
    }, {
        "name": "Crime",
        "id": 80
    }]
}, {
    "_id": ObjectId("5930eaa706fb9e365e0d55c8"),
    "m_tmdb_id": 378924,
    "m_imdb_id": "sdstt3522806",
    "m_title": "Spiderman",
    "m_genres": [{
        "name": "Action",
        "id": 28
    }, {
        "name": "Crime",
        "id": 80
    }, {
        "name": "Thriller",
        "id": 53
    }]
}]

排名电影收藏

[{
    "_id": ObjectId("59398daaad49f32c20115422"),
    "movie": ObjectId("5930ea4806fb9e365e0d55b5"),
    "count": 5
}, {
    "_id": ObjectId("59397f7095f0b91ffbdeb1f8"),
    "movie": ObjectId("583e3bd69e2ea90c69c5a254"),
    "count": 1
}, {
    "_id": ObjectId("59397f6695f0b91ffbdeb1f6"),
    "movie": ObjectId("5930eaa706fb9e365e0d55c8"),
    "count": 5
}]

如果我将m_genres名称作为Crime,Thriller。查询应该从排名电影中获取前10项,其中m_genres名称为Crime,Thriller。我能如何实现这一目标?

我的JS代码

RankMovie
    .find(queryOptions)
    .limit(10)
    .skip(1 * 10)
    .populate('movie', 'm_tmdb_id  m_title m_generes ')
    .sort('-count')
    .exec(function(err, movies) {
        if(err){
            return res.status(404).json(err);
        }
        return res.status(200).json(movies);
    }).catch(function(error) {
        return res.status(500).json(error);
    });

1 个答案:

答案 0 :(得分:1)

实际上这"仍然是"使用.aggregate()$lookup加入数据的最佳方法。

var wantedGenres = ["Action","Crime"];
var page = 1;

RankMovie.aggregate([
  { "$lookup": {
    "from": Movie.collection.name,
    "localField": "movie",
    "foreignField": "_id",
    "as": "movie"
  }},
  { "$unwind": "$movie" },
  { "$match": { "movie.m_genres.name": { "$in": wantedGenres }  }},
  { "$sort": { "count": -1 } },
  { "$project": {
    "movie": {
      "m_title": "$movie.m_title",
      "m_tmdb_id": "$movie.m_tmdb_id",
      "m_genres": "$movie.m_genres",
    },
    "count": 1
  }},
  { "$skip": 10*page },
  { "$limit": 10 },
])

由于$unwind$match合并到$lookup本身,并且只返回$match中实际符合给定条件的项目,因此效果很高而不是" ALL"相关数据。完整地在the Answer to your previous question中解释了这一点。

但这就是这三个阶段的结果:

    {
        "$lookup" : {
            "from" : "movies",
            "as" : "movie",
            "localField" : "movie",
            "foreignField" : "_id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "m_genres.name" : {
                    "$in" : [ 
                        "Action", 
                        "Crime"
                    ]
                }
            }
        }
    }

当然,我们$sort为了使匹配的结果符合正确的顺序。然后,只需使用$project选择所需字段(请注意,$addFields在此处不起作用,因为我们希望"覆盖" )并为分页执行$skip$limit

尝试这样做"客户端"因为.populate()几乎是不可能的,在实际情况下几乎是不可能的。问题当然是按类型选择"需要发生"之前"你真的开始"分页"。

所以可能发生的唯一方法是使用相对较小的" "电影"的列表,因为你确实需要在"类型"上进行选择。首先,然后执行查询的其余部分。

"完全不切实际"方式看起来像:

var wantedGenres = ["Action","Crime"];
var page = 1;

Movie.find({ "m_genres.name": { "$in": wantedGenres } })
  .select({ "_id": 1 })
  .exec()
  .then(movies =>
    RankMovie.find({ "movie": { "$in": movies.map(m => m._id) })
      .populate('movie', 'm_tmdb_id  m_title m_genres')
      .sort('-count')
      .skip(10*page)
      .limit(10)
      .exec()
  ).then(rankings => {
    // deal with response
  })
  .catch(err => res.status(500).json(err))

但当然拉动" ALL"在发出查询之前,通过网络匹配"movies"到客户端。并且可能使用$in ids的{​​{1}}参数来断开请求的BSON限制。

另一方面,汇总声明不会有相同的限制。