Mongoose Query来过滤一个数组并填充相关的内容

我试图查询属性是一个数组的引用另一个架构和一些额外的数据。 为了更好的说明,下面是模式:

var orderSchema = new Schema({ orderDate: Date, articles: [{ article: { type: Schema.Types.ObjectId, ref: 'Article' }, quantity: 'Number' }] }), Order = mongoose.model('Order', orderSchema); 

虽然我设法成功地查询参考,即:

 Order.find({}).populate('articles.article', null, { price: { $lte: 500 } }).exec(function(err, data) { for (var order of data) { for (var article of order.articles) { console.log(article); } } }); 

我有一些问题查询quantity属性,即这不起作用:

 Order.find({}).where({ 'articles.quantity': { $gte: 5 } }).populate('articles.article', null, { /*price: { $lte: 500 }*/ }).exec(function(err, data) { for (var order of data) { for (var article of order.articles) { console.log(article); } } }); 

甚至可以基于quantity查询? 如果是这样,那么最好的办法是什么?

谢谢!

更新:

问题是,结果是一个完整的数组,或者什么都没有(见更新的问题)。 我只想得到那些数量大于或等于5的logging。用你的(和我的)方法,我根本得不到任何logging(如果我设置$ gte:5001)或者两个logging(如果我设置$ gte: 5000)

 { "_id": ObjectId('56fe76c12f7174ac5018054f'), "orderDate": ISODate('2016-04-01T13:25:21.055Z'), "articles": [ { "article": ObjectId('56fe76c12f7174ac5018054b'), "quantity": 5000, "_id": ObjectId('56fe76c12f7174ac50180551') }, { "article": ObjectId('56fe76c12f7174ac5018054c'), "quantity": 1, "_id": ObjectId('56fe76c12f7174ac50180552') } ], "__v": 1 } 

您需要在这里“匹配”匹配项,因为所有的MongoDB查询都是查找“至less有一个元素”的“文档”,这个“文档” 您要求的条件“大于”

所以过滤一个“数组”和你所拥有的“查询”条件是不一样的。

一个简单的“投影”只是将“第一个”匹配的项目返回到该条件。 所以这可能不是你想要的,但作为一个例子:

 Order.find({ "articles.quantity": { "$gte": 5 } }) .select({ "articles.$": 1 }) .populate({ "path": "articles.article", "match": { "price": { "$lte": 500 } } }).exec(function(err,orders) { // populated and filtered twice } ) 

这种“sorting”是做你想做的,但问题实际上只会返回"articles"数组中的最多一个元素。

要正确地做到这一点,你需要.aggregate()过滤数组内容。 理想情况下,这是用MongoDB 3.2和$filter 。 但是在这里也有一个特殊的方法.populate()

 Order.aggregate( [ { "$match": { "artciles.quantity": { "$gte": 5 } } }, { "$project": { "orderdate": 1, "articles": { "$filter": { "input": "$articles", "as": "article", "cond": { "$gte": [ "$$article.quantity", 5 ] } } }, "__v": 1 }} ], function(err,orders) { Order.populate( orders.map(function(order) { return new Order(order) }), { "path": "articles.article", "match": { "price": { "$lte": 500 } } }, function(err,orders) { // now it's all populated and mongoose documents } ) } ) 

那么这里发生的是数组的实际“过滤”发生在.aggregate()语句中,但是当然这个结果不再是一个“mongoose文档”,因为.aggregate()一个方面是它可以“改变“文件结构,为此,mongoose”假设“是这种情况,只是返回一个”普通对象“。

这不是一个真正的问题,因为当你看到$project阶段时,我们实际上是根据定义的模式要求文档中出现的所有相同的字段。 所以即使它只是一个“简单的对象”,将它“铸造”回mongoose文件也没有问题。

这是.map()进来的地方,因为它返回一个转换后的“文档”数组,这对于下一个阶段非常重要。

现在你调用Model.populate() ,然后可以在“mongoose文档数组”上运行更多的“人口”。

结果终于是你想要的。


MongoDB早于3.2.x版本

这里唯一真正改变的就是聚合pipe道,所以这就是为了简洁而需要包含的一切。

MongoDB 2.6 – 可以使用$map$setDifference组合过滤数组。 结果是一个“集合”,但当mongoose默认在所有子文档数组上创build一个_id字段时,这不是问题:

  [ { "$match": { "artciles.quantity": { "$gte": 5 } } }, { "$project": { "orderdate": 1, "articles": { "$setDiffernce": [ { "$map": { "input": "$articles", "as": "article", "in": { "$cond": [ { "$gte": [ "$$article.price", 5 ] }, "$$article", false ] } }}, [false] ] }, "__v": 1 }} ], 

比这更旧的版本必须使用$unwind

  [ { "$match": { "artciles.quantity": { "$gte": 5 } }}, { "$unwind": "$articles" }, { "$match": { "artciles.quantity": { "$gte": 5 } }}, { "$group": { "_id": "$_id", "orderdate": { "$first": "$orderdate" }, "articles": { "$push": "$articles" }, "__v": { "$first": "$__v" } }} ], 

$ lookup Alternative

另一种替代方法是只做“服务器”上的所有内容。 这是MongoDB 3.2及更高版本的$lookup选项:

 Order.aggregate( [ { "$match": { "artciles.quantity": { "$gte": 5 } }}, { "$project": { "orderdate": 1, "articles": { "$filter": { "input": "$articles", "as": "article", "cond": { "$gte": [ "$$article.quantity", 5 ] } } }, "__v": 1 }}, { "$unwind": "$articles" }, { "$lookup": { "from": "articles", "localField": "articles.article", "foreignField": "_id", "as": "articles.article" }}, { "$unwind": "$articles.article" }, { "$group": { "_id": "$_id", "orderdate": { "$first": "$orderdate" }, "articles": { "$push": "$articles" }, "__v": { "$first": "$__v" } }}, { "$project": { "orderdate": 1, "articles": { "$filter": { "input": "$articles", "as": "article", "cond": { "$lte": [ "$$article.article.price", 500 ] } } }, "__v": 1 }} ], function(err,orders) { } ) 

虽然这些只是简单的文档,但它与您通过.populate()方法得到的结果相同。 当然,如果你真的必须的话,你总是可以在任何情况下再次“投下”mongoose的文件。

“最短”的path

这真的可以回到原来的声明,你基本上只是“接受”,“查询”并不意味着“过滤”数组内容。 .populate()可以这么做,因为它只是另一个“查询”,并且方便地在“文档”中填充。

所以,如果你真的没有通过删除原始文档数组中的其他数组成员来.filter() ,那么在后处理代码中只需要.filter()可以了:

 Order.find({ "articles.quantity": { "$gte": 5 } }) .populate({ "path": "articles.article", "match": { "price": { "$lte": 500 } } }).exec(function(err,orders) { orders = orders.filter(function(order) { order.articles = order.articles.filter(function(article) { return ( ( article.quantity >= 5 ) && ( article.article != null ) ) }); return order.aricles.length > 0; }) // orders has non matching entries removed } )