U moet de overeenkomst hier "projecteren", aangezien de MongoDB-query alleen zoekt naar een "document" met "ten minste één element" dat is "groter dan" de voorwaarde waar je om vroeg.
Dus het filteren van een "array" is niet hetzelfde als de "query"-voorwaarde die u heeft.
Een eenvoudige "projectie" zal gewoon het "eerste" overeenkomende item teruggeven aan die voorwaarde. Dus het is waarschijnlijk niet wat je wilt, maar als voorbeeld:
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
}
)
Dat "soort" doet wat je wilt, maar het probleem zal zijn dat er maar maximaal één zal terugkeren element binnen de "articles"
array.
Om dit goed te doen heb je .aggregate()
. nodig om de array-inhoud te filteren. Idealiter wordt dit gedaan met MongoDB 3.2 en $filter
. Maar er is ook een speciale manier om .populate()
hier:
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
}
)
}
)
Dus wat hier gebeurt, is dat de daadwerkelijke "filtering" van de array plaatsvindt binnen de .aggregate()
statement, maar het resultaat hiervan is natuurlijk niet langer een "mangoesdocument" omdat een aspect van .aggregate()
is dat het de documentstructuur kan "wijzigen", en om deze reden "veronderstelt" mangoest dat dit het geval is en retourneert hij gewoon een "gewoon object".
Dat is niet echt een probleem, want als je het $project
. ziet stadium vragen we eigenlijk om alle dezelfde velden die in het document aanwezig zijn volgens het gedefinieerde schema. Dus ook al is het maar een "gewoon object", er is geen probleem om het terug te "casten" in een mangoestdocument.
Dit is waar de .map()
komt binnen, omdat het een array van geconverteerde "documenten" retourneert, wat dan belangrijk is voor de volgende fase.
Nu roept u Model.populate()
. aan die vervolgens de verdere "bevolking" kan uitvoeren op de "reeks mangoestdocumenten".
Het resultaat is dan eindelijk wat je wilt.
MongoDB oudere versies dan 3.2.x
De enige dingen die hier echt veranderen, zijn de aggregatiepijplijn, dus dat is alles wat moet worden opgenomen voor de beknoptheid.
MongoDB 2.6 - Kan arrays filteren met een combinatie van $map
en $setDifference
. Het resultaat is een "set" maar dat is geen probleem wanneer mangoest een _id
aanmaakt veld standaard op alle subdocumentarrays:
[
{ "$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
}}
],
Oudere revisies van dan die moeten $unwind
. gebruiken :
[
{ "$match": { "artciles.quantity": { "$gte": 5 } }},
{ "$unwind": "$articles" },
{ "$match": { "artciles.quantity": { "$gte": 5 } }},
{ "$group": {
"_id": "$_id",
"orderdate": { "$first": "$orderdate" },
"articles": { "$push": "$articles" },
"__v": { "$first": "$__v" }
}}
],
Het $lookup-alternatief
Een ander alternatief is om in plaats daarvan alles op de "server" te doen. Dit is een optie met $lookup
van MongoDB 3.2 en hoger:
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) {
}
)
En hoewel dit gewoon documenten zijn, zijn het precies dezelfde resultaten als wat u zou hebben gekregen van de .populate()
nadering. En natuurlijk kun je altijd weer naar mangoestdocumenten gaan "casten" als het echt moet.
Het "kortste" pad
Dit gaat echt terug naar de oorspronkelijke verklaring waar je eigenlijk gewoon "accepteert" dat de "query" niet bedoeld is om de array-inhoud te "filteren". De .populate()
kan dit gelukkig doen, want het is gewoon weer een "query" en het is voor het gemak "documenten" proppen.
Dus als u echt geen "bucketloads" bandbreedte bespaart door het verwijderen van extra arrayleden in de originele documentarray, dan gewoon .filter()
ze uit in code voor nabewerking:
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
}
)