Zde musíte "promítnout" shodu, protože vše, co MongoDB dotaz dělá, je hledat "dokument", který má "alespoň jeden prvek" to je "větší než" podmínku, o kterou jste požádali.
Filtrování „pole“ tedy není totéž jako podmínka „dotaz“, kterou máte.
Jednoduchá "projekce" pouze vrátí "první" odpovídající položku do této podmínky. Takže to asi není to, co chcete, ale jako příklad:
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
}
)
Tento „druh“ dělá, co chcete, ale problém bude ve skutečnosti v tom, že se vrátí maximálně jeden prvek v rámci "articles"
pole.
Chcete-li to provést správně, potřebujete .aggregate()
pro filtrování obsahu pole. V ideálním případě se to provádí pomocí MongoDB 3.2 a $filter
. Existuje však také speciální způsob .populate()
zde:
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
}
)
}
)
Takže to, co se zde stane, je skutečné „filtrování“ pole v rámci .aggregate()
, ale výsledek z toho už samozřejmě není "mongoose document", protože jeden aspekt .aggregate()
je, že může "změnit" strukturu dokumentu, a z tohoto důvodu mongoose "předpokládá", že tomu tak je, a vrací pouze "prostý objekt".
To ve skutečnosti není problém, protože když vidíte $project
fázi, ve skutečnosti požadujeme všechna stejná pole přítomná v dokumentu podle definovaného schématu. Takže i když se jedná pouze o „prostý objekt“, není problém jej „obsadit“ zpět do mongoose dokumentu.
Zde je .map()
přichází, protože vrací pole převedených „dokumentů“, které je pak důležité pro další fázi.
Nyní zavoláte Model.populate()
který pak může spustit další „populaci“ na „souboru dokumentů mangusty“.
Výsledek je pak konečně to, co chcete.
MongoDB starší verze než 3.2.x
Jediné, co se zde skutečně mění, je agregační kanál, takže to je vše, co je potřeba pro stručnost zahrnout.
MongoDB 2.6 - Může filtrovat pole pomocí kombinace $map
a $setDifference
. Výsledkem je "set", ale to není problém, když mangoose vytvoří _id
pole ve všech polích vnořených dokumentů ve výchozím nastavení:
[
{ "$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
}}
],
Starší verze musí používat $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" }
}}
],
Alternativa $lookup
Další alternativou je místo toho dělat vše na "serveru". Toto je možnost s $lookup
MongoDB 3.2 a vyšší:
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) {
}
)
A ačkoli jsou to jen obyčejné dokumenty, jsou to stejné výsledky, jaké byste získali z .populate()
přístup. A samozřejmě můžete vždy jít a "obsadit" do dokumentů mongoose ve všech případech znovu, pokud opravdu musíte.
Nejkratší cesta
To se skutečně vrací k původnímu prohlášení, kde v podstatě jen „přijmete“, že „dotaz“ není určen k „filtrování“ obsahu pole. .populate()
může to s radostí udělat, protože je to jen další "dotaz" a cpe se do "dokumentů" z pohodlí.
Pokud tedy skutečně nešetříte „hromady“ šířky pásma odstraněním dalších členů pole v poli původního dokumentu, pak stačí .filter()
v kódu následného zpracování:
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
}
)