[解決済み] ネストされた配列の中で、マッチしたサブドキュメントの要素のみを返す
質問
メインコレクションは小売業者で、店舗用の配列が含まれています。各店舗には、オファー (この店舗で購入できるもの) の配列が含まれています。このオファー配列は、サイズの配列を持っています。(以下の例を参照ください)
次に、このサイズで利用可能なすべてのオファーを見つけようとします。
L
.
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"XS",
"S",
"M"
]
},
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
次のクエリを試してみました。
db.getCollection('retailers').find({'stores.offers.size': 'L'})
このような出力が期待できます。
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
しかし、私のクエリの出力には、マッチングしないオファーが
size
XS,X,Mです。
クエリにマッチしたオファーだけをMongoDBに返すように強制するにはどうしたらいいですか?
はじめまして、ありがとうございます。
解決方法は?
つまり、あなたが持っているクエリは、実際にその通りに "document" を選択するのです。しかし、あなたが求めているのは、クエリの条件に一致する要素のみが返されるように、含まれる配列にフィルタをかけることです。
本当の答えは、もちろん、そのような詳細な情報をフィルタリングすることで本当に多くの帯域幅を節約しているのでなければ、試すべきでもなく、少なくとも最初の位置の一致を超えるものでなければならないということです。
MongoDBには
ポジション
$
演算子
を使用すると、クエリ条件にマッチしたインデックスの配列要素を返します。ただし、これは配列の一番外側の要素にマッチした最初のインデックスを返すだけです。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
この場合は
"stores"
の配列位置のみです。つまり、もし複数の "stores" があった場合、マッチした条件を含む要素のうち "one" だけが返されることになるのです。
しかし
の内側の配列に対しては何もしません。
"offers"
そのため、一致した
"stores"
の配列が返されることに変わりはありません。
MongoDB には標準的なクエリでこれをフィルタリングする方法がないので、次のようにしてもうまくいきません。
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
MongoDBが実際にこのレベルの操作を行うためのツールは、アグリゲーションフレームワークだけです。しかし、なぜこのようなことをせず、コード内で配列のフィルタリングを行う必要があるのか、その理由は分析によって明らかになるはずです。
バージョンごとに実現できる順に説明します。
最初に
MongoDB 3.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"
}
}
}}
])
では、解説を分解してみましょう。
MongoDB 3.2.x以上
つまり一般的に言えば
$filter
は、目的を意識して設計されているので、ここではそのようにするのがよいでしょう。配列には複数のレベルがあるので、各レベルでこれを適用する必要があります。つまり、まず各レベルに飛び込んでいる
"offers"
内の
"stores"
を試験的に導入し
$filter
というコンテンツがあります。
ここで単純に比較すると
を使用しますか?
"size"
配列は私が探している要素を含んでいます"
. この論理的なコンテキストでは、簡単に言うと
$setIsSubset
の配列("セット")を比較する操作です。
["L"]
をターゲット配列に変換します。ここで、その条件は
true
( "L" を含む ) の配列要素になります。
"offers"
は保持され、結果に返される。
上位の
$filter
の結果を確認することになります。
$filter
は空の配列を返しました。
[]
に対して
"offers"
. それが空でなければ、その要素が返され、そうでなければ削除されます。
MongoDB 2.6.x
これは最近のプロセスと非常によく似ていますが
$filter
このバージョンでは
$map
を使用して各要素を検査し、その後
$setDifference
として返された要素をフィルタリングするために
false
.
だから
$map
は配列全体を返そうとしていますが
$cond
操作は、要素を返すか、代わりに
false
の値です。の比較では
$setDifference
の単一要素 "セット" に変換します。
[false]
すべて
false
の要素は削除されます。
その他の点では、上記と同じロジックです。
MongoDB 2.2.x以上
つまり、MongoDB 2.6以下では、配列を操作するための唯一のツールは
$unwind
この目的のためだけに
ない
は、この目的のためにアグリゲーションフレームワークを使用します。
手順としては、各配列を分解し、不要なものをフィルタリングして、元に戻すだけなので、確かに簡単なように見えます。主な注意点は、quot;two".にあります。
$group
の段階で、内側の配列を再構築し、次の段階で外側の配列を再構築します。また
_id
の値はすべてのレベルに存在するので、これらはグループ化の各レベルに含まれる必要があるだけです。
しかし、問題なのは
$unwind
は
非常にコストがかかる
. それでも目的はあるのですが、主な使用目的は、ドキュメントごとにこの種のフィルタリングを行うことではありません。実際、最近のリリースでは、配列の要素がグループ化キーの一部となる必要がある場合にのみ使用されるはずです。
まとめ
このように配列の複数のレベルでマッチを取得するのは簡単な処理ではありませんし、実際、次のようなことが起こり得ます。 非常に高いコスト を実装しています。
この目的のために使用すべきなのは、2つの最新のリストだけです。これらのリストでは、quot;クエリに加えてquot;シングル"パイプラインステージが採用されているからです。
$match
を行うために、quot;filtering" を行います。その結果、標準的なフォームの
.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);
})
つまり、返されたオブジェクトをquot;post;クエリ処理することは、集約パイプラインを使用してこれを行うよりもはるかに難解ではありません。また、前述のとおり、唯一の違いは、他の要素を受信時にドキュメントごとに削除するのではなく、quot;サーバーで破棄していることです。
しかし、最新のリリースでこれを行うのでなければ
のみ
$match
そして
$project
の場合、サーバーでの処理にかかるコストは、一致しない要素を最初に取り除くことでネットワークのオーバーヘッドを削減することによる利益を大きく上回ることになります。
どのような場合でも、同じ結果になります。
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}
関連
-
[解決済み] 配列のサイズが1より大きい文書を検索します。
-
[解決済み] Mongodb: ensureIndex をいつ呼び出すか?
-
[解決済み] MongoDB - シンプルなサブクエリの例
-
[解決済み] MongoDB の個別集計
-
[解決済み] ストア enum MongoDB
-
CentOS7に新規インストールしたMongodbの初期設定
-
[解決済み] 複数のキーで "distinct "を効率的に実行するには?
-
[解決済み] 特定の値を含む配列で文書を検索する
-
[解決済み] MongoDB コレクションのオブジェクト配列で、問い合わせた要素のみを取得する
-
[解決済み】MongoDBで、あるデータベースから別のデータベースにコレクションをコピーする方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] MongoDB の重複レコードを検索する
-
[解決済み] MongoDB の個別集計
-
[解決済み] mongodの書き込みに関するデフォルトの懸念はどのバージョンにありますか?
-
CentOS7に新規インストールしたMongodbの初期設定
-
[解決済み] MongoDBの命名規則とは何ですか?
-
[解決済み] SocketException: アドレスはすでに使用中です MONGODB
-
MongoDBラーニングノート
-
[解決済み】mongoコンソールでObjectIdを使用してオブジェクトを検索する方法は?
-
[解決済み】MongoDB コレクションの変更をリッスンする方法は?
-
[解決済み】RedisはmongoDBよりどれくらい速いのか?