したがって、実際のクエリでは、必要に応じて「ドキュメント」が選択されます。しかし、探しているのは、返される要素がクエリの条件にのみ一致するように、含まれている「配列をフィルタリングする」ことです。
もちろん、本当の答えは、そのような詳細を除外することによって実際に多くの帯域幅を節約していない限り、試してはいけない、または少なくとも最初の位置の一致を超えてはいけないということです。
MongoDBには位置的な$
があります クエリ条件から一致したインデックスの配列要素を返す演算子。ただし、これは「最も外側の」配列要素の「最初の」一致したインデックスのみを返します。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
この場合、それは"stores"
を意味します 配列の位置のみ。したがって、複数の「ストア」エントリがある場合、一致した条件を含む要素の「1つ」のみが返されます。 しかし 、"offers"
の内部配列には何もしません 、およびそのため、一致した"stores"
内のすべての「オファー」 配列は引き続き返されます。
MongoDBには、標準のクエリでこれを「フィルタリング」する方法がないため、以下は機能しません。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
MongoDBが実際にこのレベルの操作を行う必要がある唯一のツールは、集約フレームワークを使用することです。しかし、分析により、「おそらく」これを行うべきではなく、代わりにコードで配列をフィルタリングする必要がある理由が示されるはずです。
バージョンごとにこれを実現する方法の順に。
まずMongoDB3.2.x $filter
を使用して 操作:
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", [] ]}
}
}
}}
])
次に、 MongoDB 2.6.xを使用します 以上で$map
および$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]
]
}
}}
])
そして最後に、 MongoDB 2.2.xより上のバージョンでは 集約フレームワークが導入された場所。
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"
}
}
}}
])
説明を分解してみましょう。
MongoDB3.2.x以降
つまり、一般的に言って、$filter
目的を念頭に置いて設計されているので、ここに行く方法です。配列には複数のレベルがあるため、各レベルでこれを適用する必要があります。したがって、最初に各"offers"
に飛び込みます "stores"
内 試験と$filter
そのコンテンツ。
ここでの簡単な比較は、 "Does "size"
です。 配列には、探している要素が含まれています」 。この論理的なコンテキストでは、簡単なことは$setIsSubset
を使用することです。 ["L"]
の配列( "set")を比較する操作 ターゲットアレイに。その条件がtrue
の場合 (「L」が含まれています)次に、"offers"
の配列要素 保持され、結果に返されます。
上位レベルの$filter
、次に、その前の$filter
の結果かどうかを確認します。 空の配列を返しました[]
"offers"
の場合 。空でない場合は要素が返されるか、そうでない場合は削除されます。
MongoDB 2.6.x
これは、$filter
がないことを除けば、最新のプロセスと非常によく似ています。 このバージョンでは、$map
を使用できます 各要素を検査してから、$setDifference
を使用します false
として返された要素を除外します 。
つまり、$map
配列全体を返しますが、$cond
操作は、要素を返すか、代わりにfalse
を返すかを決定するだけです。 価値。 $setDifference
の比較 [false]
の単一の要素「セット」に すべてのfalse
返された配列の要素は削除されます。
他のすべての点で、ロジックは上記と同じです。
MongoDB2.2.x以降
したがって、MongoDB 2.6では、配列を操作するための唯一のツールは$unwind
です。 、そしてこの目的だけのためにあなたはすべきではありません この目的のために「ちょうど」集約フレームワークを使用してください。
各アレイを「分解」し、不要なものを除外してから元に戻すだけで、プロセスは確かに単純に見えます。主な注意点は「2つの」$group
です。 ステージ。「最初の」は内部アレイを再構築し、次は外部アレイを再構築します。明確な_id
があります すべてのレベルの値なので、これらはグループ化のすべてのレベルに含める必要があります。
しかし、問題は$unwind
非常にコストがかかる 。それでも目的はありますが、主な使用目的は、ドキュメントごとにこの種のフィルタリングを行わないことです。実際、最近のリリースでは、配列の要素が「グループ化キー」自体の一部になる必要がある場合にのみ使用する必要があります。
結論
したがって、このような配列の複数のレベルで一致を取得するのは簡単なプロセスではなく、実際には非常にコストがかかる可能性があります。 正しく実装されていない場合。
「クエリ」$match
に加えて「単一の」パイプラインステージを採用しているため、この目的には2つの最新のリストのみを使用する必要があります。 「フィルタリング」を行うために。結果として得られる効果は、標準形式の.find()
よりも少しオーバーヘッドが大きくなります。 。
ただし、一般的に、これらのリストにはまだかなりの複雑さがあります。実際、サーバーとクライアント間で使用される帯域幅を大幅に改善する方法で、このようなフィルタリングによって返されるコンテンツを大幅に削減しない限り、より優れています。最初のクエリと基本的な予測の結果をフィルタリングします。
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);
})
したがって、返されたオブジェクトの「ポスト」クエリ処理を操作することは、集約パイプラインを使用してこれを実行するよりもはるかに鈍感ではありません。また、前述のように、唯一の「実際の」違いは、受信時に「ドキュメントごと」に削除するのではなく、「サーバー」上の他の要素を破棄することです。これにより、帯域幅が少し節約される可能性があります。
ただし、のみの最新リリースでこれを行っている場合を除きます。 $match
および$project
、その場合、サーバーでの処理の「コスト」は、最初に一致しない要素を取り除くことによって、そのネットワークオーバーヘッドを削減する「利益」を大幅に上回ります。
いずれの場合も、同じ結果が得られます:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}