Die Abfrage, die Sie haben, wählt also das "Dokument" genau so aus, wie es sollte. Sie suchen jedoch nach "Filtern der enthaltenen Arrays", sodass die zurückgegebenen Elemente nur der Bedingung der Abfrage entsprechen.
Die eigentliche Antwort lautet natürlich: Wenn Sie nicht wirklich viel Bandbreite sparen, indem Sie solche Details herausfiltern, sollten Sie es nicht einmal versuchen oder zumindest über die erste Positionsübereinstimmung hinaus.
MongoDB verfügt über einen Positionsoperator $
, der ein Array-Element am übereinstimmenden Index aus einer Abfragebedingung zurückgibt. Dies gibt jedoch nur den "ersten" übereinstimmenden Index des "äußersten" Array-Elements zurück.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
In diesem Fall bedeutet dies nur die "stores"
Array-Position. Wenn also mehrere "Speicher" -Einträge vorhanden wären, würde nur "eines" der Elemente zurückgegeben, die Ihre übereinstimmende Bedingung enthielten. Dies bedeutet jedoch nichts für das innere Array von "offers"
, und als solches würde jedes "Angebot" innerhalb des übereinstimmenden "stores"
Arrays immer noch zurückgegeben.
MongoDB hat keine Möglichkeit, dies in einer Standardabfrage zu "filtern", daher funktioniert Folgendes nicht:
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
Das einzige Werkzeug, das MongoDB tatsächlich benötigt, um diese Manipulationsebene auszuführen, ist das Aggregationsframework. Die Analyse sollte Ihnen jedoch zeigen, warum Sie dies "wahrscheinlich" nicht tun sollten, und stattdessen nur das Array im Code filtern.
In der Reihenfolge, wie Sie dies pro Version erreichen können.
Zuerst mit MongoDB 3.2.x mit der $filter
Operation:
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", [] ]}
}
}
}}
])
Dann mit MongoDB 2.6.x und höher mit $map
und $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]
]
}
}}
])
Und schließlich in jeder Version über MongoDB 2.2.x, in der das Aggregationsframework eingeführt wurde.
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"
}
}
}}
])
Lassen Sie uns die Erklärungen aufschlüsseln.
MongoDB 3.2.x und höher
Im Allgemeinen $filter
ist dies der richtige Weg, da es auf den Zweck ausgelegt ist. Da es mehrere Ebenen des Arrays gibt, müssen Sie dies auf jeder Ebene anwenden. Also zunächst sind Sie tauchen in jeden "offers"
innerhalb "stores"
zu examime und $filter
diese Inhalte.
Der einfache Vergleich hier lautet "Enthält das "size"
Array das gesuchte Element?" . In diesem logischen Kontext besteht die kurze Aufgabe darin, die $setIsSubset
Operation zum Vergleichen eines Arrays ("Set") ["L"]
mit dem Zielarray zu verwenden. Wenn diese Bedingung erfüllt ist true
(sie enthält "L"), wird das Array-Element für "offers"
beibehalten und im Ergebnis zurückgegeben.
In der höheren Ebene $filter
suchen Sie dann, ob das Ergebnis dieses vorherigen $filter
ein leeres Array []
für zurückgegeben hat "offers"
. Wenn es nicht leer ist, wird das Element zurückgegeben oder auf andere Weise entfernt.
MongoDB 2.6.x.
Dies ist dem modernen Prozess sehr ähnlich, außer dass Sie, da es $filter
in dieser Version keine gibt $map
, jedes Element überprüfen und dann $setDifference
alle Elemente herausfiltern können, die als zurückgegeben wurden false
.
Es wird also $map
das gesamte Array zurückgegeben, aber die $cond
Operation entscheidet nur, ob das Element oder stattdessen ein false
Wert zurückgegeben wird. Beim Vergleich $setDifference
mit einem einzelnen Element würde "Menge" [false]
aller false
Elemente im zurückgegebenen Array entfernt.
In allen anderen Fällen ist die Logik dieselbe wie oben.
MongoDB 2.2.x und höher
So unter MongoDB 2.6 das einzige Werkzeug für mit Arrays arbeiten ist $unwind
, und zu diesem Zweck allein sollten Sie nicht die Aggregation Rahmen „nur“ für diesen Zweck verwenden.
Der Prozess erscheint in der Tat einfach, indem Sie einfach jedes Array "auseinander nehmen", die Dinge herausfiltern, die Sie nicht benötigen, und es dann wieder zusammensetzen. Die Hauptaufgabe liegt in den "zwei" $group
Phasen, wobei die "erste" das innere Array neu erstellt und die nächste das äußere Array neu erstellt. Auf _id
allen Ebenen gibt es unterschiedliche Werte, daher müssen diese nur auf jeder Gruppierungsebene berücksichtigt werden.
Aber das Problem ist , dass $unwind
ist sehr teuer . Obwohl es immer noch einen Zweck hat, besteht seine Hauptverwendungsabsicht darin, diese Art der Filterung nicht pro Dokument durchzuführen. Tatsächlich sollte es in modernen Releases nur verwendet werden, wenn ein Element des Arrays Teil des "Gruppierungsschlüssels" selbst werden muss.
Fazit
Es ist also kein einfacher Prozess, Übereinstimmungen auf mehreren Ebenen eines Arrays wie dieses zu erhalten, und tatsächlich kann es bei falscher Implementierung äußerst kostspielig sein .
Zu diesem Zweck sollten immer nur die beiden modernen Listings verwendet werden, da sie zusätzlich zur "Abfrage" eine "einzelne" Pipeline-Stufe $match
verwenden, um die "Filterung" durchzuführen. Der resultierende Effekt ist wenig mehr Aufwand als die Standardformen von .find()
.
Im Allgemeinen sind diese Einträge jedoch immer noch sehr komplex. Wenn Sie den durch eine solche Filterung zurückgegebenen Inhalt nicht drastisch reduzieren, um die zwischen Server und Client verwendete Bandbreite erheblich zu verbessern, sind Sie besser des Filterns des Ergebnisses der anfänglichen Abfrage und der Grundprojektion.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
).forEach(function(doc) {
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);
})
Die Arbeit mit der Abfrageverarbeitung des zurückgegebenen Objekts "post" ist daher weitaus weniger stumpf als die Verwendung der Aggregationspipeline, um dies zu tun. Und wie bereits erwähnt, besteht der einzige "echte" Unterschied darin, dass Sie die anderen Elemente auf dem "Server" verwerfen, anstatt sie beim Empfang "pro Dokument" zu entfernen, was möglicherweise ein wenig Bandbreite spart.
Wenn Sie dies jedoch nicht in einer modernen Version mit nur $match
und tun, $project
überwiegen die "Kosten" für die Verarbeitung auf dem Server erheblich den "Gewinn" bei der Reduzierung des Netzwerk-Overheads, indem zuerst die nicht übereinstimmenden Elemente entfernt werden.
In allen Fällen erhalten Sie das gleiche Ergebnis:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}
db.getCollection('retailers').find({'stores.offers.size': 'L'}, {'stores.offers': 1})
. Aber dann enthält die Antwort auch falsche Angebote