Musisz tu „zaprojektować” dopasowanie, ponieważ jedyne, co robi zapytanie MongoDB, to szukanie „dokumentu”, który ma „co najmniej jeden element” czyli "większe niż" stan, o który prosiłeś.
Tak więc filtrowanie „tablicy” to nie to samo, co warunek „zapytanie”.
Prosta „projekcja” po prostu przywróci „pierwszy” dopasowany element do tego warunku. Więc prawdopodobnie nie tego chcesz, ale jako przykład:
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
}
)
Ten „rodzaj” robi to, co chcesz, ale tak naprawdę problem będzie polegał na tym, że powróci tylko co najwyżej jeden element w "articles"
tablica.
Aby zrobić to poprawnie, potrzebujesz .aggregate()
do filtrowania zawartości tablicy. Najlepiej zrobić to za pomocą MongoDB 3.2 i $filter
. Ale jest też specjalny sposób na .populate()
tutaj:
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
}
)
}
)
To, co się tutaj dzieje, to faktyczne „filtrowanie” tablicy w ramach .aggregate()
oświadczenie, ale oczywiście wynik tego nie jest już "dokumentem mangusty", ponieważ jeden aspekt .aggregate()
jest to, że może „zmienić” strukturę dokumentu, iz tego powodu mangusta „zakłada”, że tak jest i po prostu zwraca „zwykły obiekt”.
To naprawdę nie jest problem, ponieważ kiedy widzisz $project
na etapie, w rzeczywistości prosimy o wszystkie te same pola obecne w dokumencie zgodnie ze zdefiniowanym schematem. Więc nawet jeśli jest to tylko „zwykły obiekt”, nie ma problemu z „przerzuceniem” go z powrotem do dokumentu z mangustą.
To tutaj .map()
pojawia się, ponieważ zwraca tablicę przekonwertowanych „dokumentów”, co jest następnie ważne w następnym etapie.
Teraz wywołujesz Model.populate()
który może następnie uruchomić dalszą „populację” na „tablicy dokumentów dotyczących mangusty”.
Rezultat jest w końcu taki, jaki chcesz.
MongoDB starsze wersje niż 3.2.x
Jedyne, co naprawdę się tutaj zmienia, to potok agregacji, więc to wszystko, co należy uwzględnić, aby uzyskać zwięzłość.
MongoDB 2.6 - Może filtrować tablice za pomocą kombinacji $map
i $setDifference
. Wynikiem jest „zestaw”, ale nie stanowi to problemu, gdy mangusta tworzy _id
domyślnie we wszystkich tablicach dokumentów podrzędnych:
[
{ "$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
}}
],
Starsze wersje muszą używać $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" }
}}
],
Alternatywna wyszukiwarka $
Inną alternatywą jest zrobienie wszystkiego na „serwerze”. To jest opcja z $lookup
MongoDB 3.2 i nowszych:
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) {
}
)
I chociaż są to tylko zwykłe dokumenty, są to dokładnie te same wyniki, które można uzyskać z .populate()
zbliżać się. I oczywiście zawsze możesz ponownie „przesłać” dokumenty z mangusty we wszystkich przypadkach, jeśli naprawdę musisz.
Najkrótsza ścieżka
To naprawdę wraca do oryginalnej instrukcji, w której po prostu „zaakceptujesz”, że „zapytanie” nie ma na celu „filtrowania” zawartości tablicy. .populate()
może z radością to zrobić, ponieważ jest to tylko kolejne „zapytanie” i jest dla wygody umieszczane w „dokumentach”.
Więc jeśli naprawdę nie oszczędzasz „zasobników” przepustowości przez usunięcie dodatkowych elementów tablicy z oryginalnej tablicy dokumentów, po prostu .filter()
je w kodzie przetwarzania końcowego:
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
}
)