Takže dotaz, který máte, ve skutečnosti vybere "dokument" přesně tak, jak by měl. Ale co hledáte, je "filtrovat obsažená pole" tak, aby vrácené prvky odpovídaly pouze podmínce dotazu.
Skutečnou odpovědí je samozřejmě to, že pokud skutečně neušetříte spoustu šířky pásma odfiltrováním takových detailů, neměli byste to ani zkoušet, nebo alespoň po první poziční shodu.
MongoDB má poziční $
operátor, který vrátí prvek pole na odpovídajícím indexu z podmínky dotazu. Toto však vrátí pouze „první“ odpovídající index „vnějšího“ prvku pole nejvíce.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
V tomto případě to znamená "stores"
pouze pozice pole. Pokud by tedy existovalo více položek „obchody“, bude vrácen pouze „jeden“ z prvků, které obsahovaly vaši shodu. Ale , který nedělá nic pro vnitřní pole "offers"
a jako taková každá "nabídka" v rámci odpovídajících "stores"
pole by bylo stále vráceno.
MongoDB nemá žádný způsob, jak to "filtrovat" ve standardním dotazu, takže následující nefunguje:
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
Jedinými nástroji, které MongoDB skutečně má k provedení této úrovně manipulace, je agregační rámec. Ale analýza by vám měla ukázat, proč byste to „pravděpodobně“ neměli dělat a místo toho pole filtrovat v kódu.
V pořadí, jak toho můžete dosáhnout podle verze.
Nejprve s MongoDB 3.2.x pomocí $filter
operace:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$filter": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$filter": {
"input": "$$store.offers",
"as": "offer",
"cond": {
"$setIsSubset": [ ["L"], "$$offer.size" ]
}
}
}
}
}
},
"as": "store",
"cond": { "$ne": [ "$$store.offers", [] ]}
}
}
}}
])
Poté pomocí MongoDB 2.6.x a výše pomocí $map
a $setDifference
:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$setDifference": [
{ "$map": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$setDifference": [
{ "$map": {
"input": "$$store.offers",
"as": "offer",
"in": {
"$cond": {
"if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
"then": "$$offer",
"else": false
}
}
}},
[false]
]
}
}
}
},
"as": "store",
"in": {
"$cond": {
"if": { "$ne": [ "$$store.offers", [] ] },
"then": "$$store",
"else": false
}
}
}},
[false]
]
}
}}
])
A konečně v jakékoli verzi výše MongoDB 2.2.x kde byl představen agregační rámec.
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$unwind": "$stores" },
{ "$unwind": "$stores.offers" },
{ "$match": { "stores.offers.size": "L" } },
{ "$group": {
"_id": {
"_id": "$_id",
"storeId": "$stores._id",
},
"offers": { "$push": "$stores.offers" }
}},
{ "$group": {
"_id": "$_id._id",
"stores": {
"$push": {
"_id": "$_id.storeId",
"offers": "$offers"
}
}
}}
])
Pojďme si vysvětlit vysvětlení.
MongoDB 3.2.xa vyšší
Obecně řečeno, $filter
je způsob, jak jít sem, protože je navržen s ohledem na účel. Protože existuje více úrovní pole, musíte to použít na každé úrovni. Nejprve se tedy ponoříte do každé "offers"
v "stores"
prozkoumat a $filter
ten obsah.
Zde je jednoduché srovnání "Vyhovuje "size"
pole obsahuje prvek, který hledám" . V tomto logickém kontextu stačí použít $setIsSubset
operace pro porovnání pole ("set") ["L"]
do cílového pole. Kde je tato podmínka true
( obsahuje "L" ), pak prvek pole pro "offers"
je zachován a vrácen ve výsledku.
Ve vyšší úrovni $filter
, pak se díváte, zda výsledek z předchozího $filter
vrátil prázdné pole []
pro "offers"
. Pokud není prázdný, je prvek vrácen nebo je jinak odstraněn.
MongoDB 2.6.x
Toto je velmi podobné modernímu procesu s tím rozdílem, že neexistuje žádný $filter
v této verzi můžete použít $map
prozkoumejte každý prvek a poté použijte $setDifference
odfiltrovat všechny prvky, které byly vráceny jako false
.
Takže $map
vrátí celé pole, ale $cond
operace pouze rozhodne, zda vrátí prvek nebo místo toho false
hodnota. Ve srovnání $setDifference
na jeden prvek "set" [false]
všechny false
prvky ve vráceném poli by byly odstraněny.
Ve všech ostatních ohledech je logika stejná jako výše.
MongoDB 2.2.xa vyšší
Takže pod MongoDB 2.6 je jediným nástrojem pro práci s poli $unwind
a pouze pro tento účel byste neměli použijte agregační rámec „jen“ pro tento účel.
Proces se skutečně zdá být jednoduchý, jednoduše „rozebrat“ každé pole, odfiltrovat věci, které nepotřebujete, a pak je dát zpět dohromady. Hlavní péče je ve "dvojce" $group
fáze, přičemž „první“ přebuduje vnitřní pole a další přestaví vnější pole. Existují různé _id
hodnoty na všech úrovních, takže je stačí zahrnout na každé úrovni seskupení.
Problém je ale v tom, že $unwind
je velmi nákladné . Ačkoli to má stále svůj účel, hlavním účelem použití není provádět tento druh filtrování na dokument. Ve skutečnosti by se v moderních verzích mělo používat pouze tehdy, když se prvek pole (polí) musí stát součástí samotného "klíče seskupení".
Závěr
Není tedy jednoduchý proces získat shody na více úrovních pole, jako je tento, a ve skutečnosti to může být extrémně nákladné při nesprávné implementaci.
K tomuto účelu by se měly vždy používat pouze dva moderní výpisy, protože kromě „dotazu“ $match
využívají „jedinou“ fázi potrubí. za účelem provedení „filtrace“. Výsledný efekt je o něco vyšší než u standardních forem .find()
.
Obecně však platí, že tyto výpisy mají stále určitou složitost, a pokud skutečně drasticky nezmenšíte obsah vrácený takovým filtrováním způsobem, který výrazně zlepší šířku pásma používaného mezi serverem a klientem, pak je lepší. filtrování výsledku počátečního dotazu a základní projekce.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
).forEach(function(doc) {
// Technically this is only "one" store. So omit the projection
// if you wanted more than "one" match
doc.stores = doc.stores.filter(function(store) {
store.offers = store.offers.filter(function(offer) {
return offer.size.indexOf("L") != -1;
});
return store.offers.length != 0;
});
printjson(doc);
})
Práce se zpracováním dotazu „post“ vráceného objektu je tedy mnohem méně obtížná než použití agregačního kanálu. A jak bylo uvedeno, jediným „skutečným“ rozdílem by bylo to, že zahazujete ostatní prvky na „serveru“ namísto toho, abyste je odebírali „na dokument“ po přijetí, což může ušetřit trochu šířky pásma.
Ale pokud to neděláte v moderní verzi s pouze $match
a $project
, pak "náklady" na zpracování na serveru výrazně převáží "zisk" ze snížení režie sítě tím, že se nejprve odstraní nesrovnatelné prvky.
Ve všech případech získáte stejný výsledek:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}