Тук трябва да "проектирате" съвпадението, тъй като всичко, което заявката MongoDB прави, е да търси "документ", който има "поне един елемент" което е „по-голямо от“ условието, което поискахте.
Така че филтрирането на „масив“ не е същото като условието „заявка“, което имате.
Една проста "проекция" просто ще върне "първия" съответстващ елемент към това условие. Така че вероятно не е това, което искате, а като пример:
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
}
)
Това "нещо" прави това, което искате, но проблемът наистина ще бъде, че винаги ще се върне само един елемент в "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()
изявление, но разбира се резултатът от това вече не е "документ на мангуста", защото един аспект на .aggregate()
е, че може да "промени" структурата на документа и поради тази причина mongoose "предполага", че е така и просто връща "обикновен обект".
Това всъщност не е проблем, тъй като когато видите $project
етап, ние всъщност искаме всички същите полета, присъстващи в документа, съгласно дефинираната схема. Така че, въпреки че е просто „обикновен обект“, няма проблем да го „прехвърляте“ обратно в документ от мангуста.
Това е мястото, където .map()
идва, тъй като връща масив от преобразувани „документи“, което след това е важно за следващия етап.
Сега извиквате Model.populate()
който след това може да изпълнява по-нататъшното "популация" в "масива от документи на мангуста".
Тогава резултатът най-накрая е това, което искате.
По-стари версии на MongoDB от 3.2.x
Единствените неща, които наистина се променят тук, са конвейерът за агрегиране, така че това е всичко, което трябва да бъде включено за краткост.
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
}}
],
По-старите ревизии от това трябва да използват $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
Друга алтернатива е просто да правите всичко на "сървъра". Това е опция с $lookup
на MongoDB 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) {
}
)
И въпреки че това са просто обикновени документи, резултатите са същите като тези, които бихте получили от .populate()
Приближаване. И, разбира се, винаги можете да отидете и да „прехвърляте“ към документите на мангусти във всички случаи отново, ако наистина трябва.
Най-краткият път
Това наистина се връща към оригиналната декларация, където по същество просто "приемате", че "заявката" не е предназначена да "филтрира" съдържанието на масива. .populate()
с радост може да го направи, защото това е просто поредната „заявка“ и се пълни с „документи“ за удобство.
Така че, ако наистина не спестявате "кофи" от честотна лента чрез премахване на допълнителни членове на масива в оригиналния масив от документи, тогава просто .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
}
)