node.js Mongoose Query 过滤数组并填充相关内容
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36371190/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mongoose Query to filter an array and Populate related content
提问by uglycode
I'm trying to query the property that is an array of both reference to another schema and some additional data. For better clarification, here's the schema:
我正在尝试查询属性,该属性是对另一个架构和一些附加数据的引用的数组。为了更好地说明,这里是架构:
var orderSchema = new Schema({
orderDate: Date,
articles: [{
article: {
type: Schema.Types.ObjectId,
ref: 'Article'
},
quantity: 'Number'
}]
}),
Order = mongoose.model('Order', orderSchema);
While I managed to successfully query the reference, i.e.:
虽然我设法成功查询了参考,即:
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);
}
}
});
I have some issues querying the quantityattribute, i.e. this doesn't work:
我在查询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);
}
}
});
Is it even possible to base the query on quantity? And if so, what would be the best approach?
甚至可以基于查询quantity吗?如果是这样,最好的方法是什么?
Thank you!
谢谢!
UPDATE:
更新:
The problem is, the result is either a complete array, or nothing (see updated question). I want to get only those records that have quantity more or the same as 5. With your (and mine) approach I get either no records at all (if I set $gte: 5001) or both records (if I set $gte:5000)
问题是,结果要么是一个完整的数组,要么什么都没有(请参阅更新的问题)。我只想获得数量大于或等于 5 的那些记录。使用您(和我的)方法,我要么根本没有记录(如果我设置 $gte: 5001)或两个记录(如果我设置 $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
}
采纳答案by Blakes Seven
You need to "project" the match here since all the MongoDB query does is look for a "document" that has "at least one element"that is "greater than"the condition you asked for.
您需要在“项目”在这里比赛,因为所有的MongoDB的查询做的就是寻找一个“文件”具有“至少一个元素”是“大于”你问的条件。
So filtering an "array" is not the same as the "query" condition you have.
因此过滤“数组”与您拥有的“查询”条件不同。
A simple "projection" will just return the "first" matched item to that condtion. So it's probably not what you want, but as an example:
一个简单的“投影”只会将“第一个”匹配项返回到该条件。所以这可能不是你想要的,但作为一个例子:
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
}
)
That "sort of" does what you want, but the problem is really going to be that will only ever return at most oneelement within the "articles"array.
这种“某种”可以满足您的需求,但问题实际上是最多只会返回数组中的一个元素"articles"。
To do this properly you need .aggregate()to filter the array content. Ideally this is done with MongoDB 3.2 and $filter. But there is also a special way to .populate()here:
要正确执行此操作,您需要.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
}
)
}
)
So what happens here is the actual "filtering" of the array happens within the .aggregate()statement, but of course the result from this is no longer a "mongoose document" because one aspect of .aggregate()is that it can "alter" the document structure, and for this reason mongoose "presumes" that is the case and just returns a "plain object".
所以这里发生的是数组的实际“过滤”发生在.aggregate()语句中,但当然由此产生的结果不再是“猫鼬文档”,因为一方面.aggregate()它可以“改变”文档结构,并且对于这个原因猫鼬“假设”是这种情况,只是返回一个“普通对象”。
That's not really a problem, since when you see the $projectstage, we are actually asking for all of the same fields present in the document according to the defined schema. So even though it's just a "plain object" there is no problem "casting" it back into an mongoose document.
这不是真正的问题,因为当您看到$project舞台时,我们实际上是根据定义的模式要求文档中存在的所有相同字段。因此,即使它只是一个“普通对象”,将它“转换”回猫鼬文档也没有问题。
This is where the .map()comes in, as it returns an array of converted "documents", which is then important for the next stage.
这就是.map()进来的地方,因为它返回一组转换后的“文档”,这对于下一阶段很重要。
Now you call Model.populate()which can then run the further "population" on the "array of mongoose documents".
现在您调用Model.populate()which 然后可以在“猫鼬文档数组”上运行进一步的“人口”。
The result then is finally what you want.
结果最终就是你想要的。
MongoDB older versions than 3.2.x
MongoDB 比 3.2.x 旧的版本
The only things that really change here are the aggregation pipeline, So that is all that needs to be included for brevity.
这里唯一真正改变的是聚合管道,所以为了简洁起见,这就是所有需要包含的内容。
MongoDB 2.6- Can filter arrays with a combination of $mapand $setDifference. The result is a "set" but that is not a problem when mongoose creates an _idfield on all sub-document arrays by default:
MongoDB的2.6-可与组合滤波器阵列$map和$setDifference。结果是一个“集合”,但是当 mongoose_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
}}
],
Older revisions of than that must use $unwind:
比这更旧的修订版必须使用$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" }
}}
],
The $lookup Alternative
$lookup 替代方案
Another alternate is to just do everything on the "server" instead. This is an option with $lookupof MongoDB 3.2 and greater:
另一种替代方法是只在“服务器”上执行所有操作。这是$lookupMongoDB 3.2 及更高版本的一个选项:
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) {
}
)
And though those are just plain documents, it's just the same results as what you would have got from the .populate()approach. And of course you can always go and "cast" to mongoose documents in all cases again if you really must.
尽管这些只是简单的文档,但它的结果与您从该.populate()方法中得到的结果相同。当然,如果你真的必须,你可以在所有情况下再次“投射”到猫鼬文档。
The "shortest" Path
“最短”路径
This really goes back to the orginal statement where you basically just "accept" that the "query" is not meant to "filter" the array content. The .populate()can happilly do so becuse it's just another "query" and is stuffing in "documents" by convenience.
这真的可以追溯到原始语句,您基本上只是“接受”“查询”并不意味着“过滤”数组内容。该.populate()可happilly这样做becuse它只是一个“查询”,并在方便的“文档”馅。
So if you really are not saving "bucketloads" of bandwith by the removal of additional array members in the orginal document array, then just .filter()them out in post processing code:
因此,如果您确实没有通过删除原始文档数组中的其他数组成员来节省带宽的“bucketloads”,那么.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
}
)
回答by hamidreza nikoonia
exports.getStudentBy = async (req, res) => {
try {
// get search_criteria from query parameter
// build a query object with it
// send data to the frontend
const { search_field, search_value } = req.query;
const queryObj = {};
if (search_field !== '' && search_value !== '') {
queryObj[search_field] = search_value;
}
const student = await Student.find(queryObj);

