MongoDB 2.1の新しい集約フレームワーク

リリース2.1 では、新しいデータ集約フレームワークなどの機能実装が発表されました 。 この非常に興味深いことの第一印象についてお話したいと思います。 この機能により、SQLのようにフィールドをグループ化するように設計された非常に単純な構造を優先して、Map / Reduceを放棄し、JavaScriptコードを書くことができる場所があるはずです。







イノベーションに関するドキュメントは、公式Webサイトの対応するセクションにあります。 まず、これがどのように機能し、どのMongoDBコンストラクトが役立つかを見てみましょう。



そのため、MongoDBからデータを取得する際の主な困難は、いくつかの個別の要素内に含まれる配列とデータを操作することです。 はい、SQLの場合と同様に選択できますが、取得時に直接集約することはできません。 新しいフレームワークは、特別な演算子のチェーンに基づいて、そのようなデータを操作する宣言的な方法です(そのうちの7つのみです)。 UNIXの場合と同様に、サンプルデータは1つの演算子の出力から別の演算子の入力に転送されます。 部分的に新しいオペレーターの助けを借りて、既存のオペレーターを繰り返すことができます。 テストコレクションを、人に関するデータを格納するコレクションとします。 標準サンプル:



db.test.find({name: "Ivan"});
      
      





同様になります



 db.test.aggregate({$match: {name: "Ivan"}});
      
      





ただし、2番目の例では、演算子をコンマでリストすることでデータ処理チェーンを構築できるため、すべてが少し興味深いものです。 ソートには、$ソート演算子を使用します。次に例を示します。



 db.test.aggregate({$match: {name: "Ivan"}}, {$sort: {age: 1}});
      
      





そのため、「Ivan」という名前のすべての人々をWebで検索し、年齢別にサンプルをソートします。 そして、最も古いIvanを選択するには、1つの要素で選択範囲をカットする必要があります。



 db.test.aggregate({$match: {name: "Ivan"}}, {$sort: {age: -1}}, {$limit: 1});
      
      





これは既存の機能主義の繰り返しだと言うでしょう。 はい、ある程度は可能ですが、サンプルをより柔軟に操作できる新しい演算子は考慮していません。 それらをより詳細に分析します。



オペレーター$プロジェクト




フィールドを操作するように設計されており、新しいフィールドを追加したり、入力時に受け取ったドキュメント内のフィールドを削除したり、名前を変更したりできます。 次の構成は、ドキュメントフローにユーザーの名前と年齢のみを含めます(フィルターアウト)。



 {$project: {name: 1, age: 1}}
      
      





2つのフィールドのみを持つすべてのドキュメントは、次の演算子の入力に到達します。ストリームには他のフィールドはありません(_idフィールドを除き、除外するには、_id:0を明示的に指定する必要があります)。 番号1には含まれ、番号0にはフィールドの送信は含まれません。 さらに、この演算子を使用すると、フィールドの名前を変更したり、フィールドの埋め込みオブジェクトからフィールドを「取得」したり、 計算に基づいて新しいフィールドを追加したりできます



$巻き戻し演算子




私の意見では、これが最も興味深いオペレーターです。 選択したドキュメントの要素ごとにネストされた配列を「拡張」できます。 たとえば、次のような人々の基盤があるとします。



 db.test.insert({name: "Ivan", likes: ["Maria", "Anna"]}); db.test.insert({name: "Serge", likes: ["Anna"]});
      
      





likesフィールドは、どの女の子がどの男の子を好むかを意味します。 $ unwind演算子を適用します。



 db.test.aggregate({$unwind: "$likes"});
      
      





 { "result" : [ { "_id" : ObjectId("4f598de76a8f8bc74573e9fd"), "name" : "Ivan", "likes" : "Maria" }, { "_id" : ObjectId("4f598de76a8f8bc74573e9fd"), "name" : "Ivan", "likes" : "Anna" }, { "_id" : ObjectId("4f598e086a8f8bc74573e9fe"), "name" : "Serge", "likes" : "Anna" } ], "ok" : 1 }
      
      





likes配列が拡張され、各文書に以前のすべての配列値を含むlikesフィールドが追加されたことがわかります。 最も人気のある女の子を見つけたい場合は、いいねフィールドで選択をグループ化します。 グループ化するには、次の演算子を使用します。



$グループオペレーター




便宜上、選択範囲を数字1で埋められた別のフィールドで補完します(この方法で簡単に要約できます)。



 db.test.aggregate({$unwind: "$likes"}, {$project: {name:1, likes:1, count: {$add: [1]}}});
      
      





 { "result" : [ { "_id" : ObjectId("4f598de76a8f8bc74573e9fd"), "name" : "Ivan", "likes" : "Maria", "count" : 1 }, { "_id" : ObjectId("4f598de76a8f8bc74573e9fd"), "name" : "Ivan", "likes" : "Anna", "count" : 1 }, { "_id" : ObjectId("4f598e086a8f8bc74573e9fe"), "name" : "Serge", "likes" : "Anna", "count" : 1 } ], "ok" : 1 }
      
      





これにより、$ sum集計演算子を使用できます。 つまり、カウントフィールドの値を毎回数値フィールドに追加し、女の子の名前を含むいいね!フィールドでサンプル全体をグループ化します。



 db.test.aggregate({$unwind: "$likes"}, {$project: {name:1, likes:1, count: {$add: [1]}}}, {$group: {_id: "$likes", number: {$sum: "$count"}}});
      
      





 { "result" : [ { "_id" : "Anna", "number" : 2 }, { "_id" : "Maria", "number" : 1 } ], "ok" : 1 }
      
      





出力をソートして1つのドキュメントのみに制限することは残ります。



 db.test.aggregate({$unwind: "$likes"}, {$project: {name:1, likes:1, count: {$add: [1]}}}, {$group: {_id: "$likes", number: {$sum: "$count"}}}, {$sort: {number: -1}}, {$limit: 1});
      
      







 { "result" : [ { "_id" : "Anna", "number" : 2 } ], "ok" : 1 }
      
      







私たちの最も人気のある女の子はアンナです。



そして今、具体的な例。





新しい機会に純粋に浸透するために、動物園の動物に関するデータを保存し、データ集約のいくつかの問題を解決するコレクションがあると仮定します。 ここに私たちの足と尾があります:



 db.zoo.insert({name: "Lion", ration: [{meat: 20}, {fish: 1}, {water: 30}], holidays: [1,4], staff: {like: ["Petrovich", "Mihalich"], dislike: "Maria"}}); db.zoo.insert({name: "Tiger", ration: [{meat: 15}, {water: 25}], holidays: [6], staff: {like: ["Petrovich", "Maria"]}}); db.zoo.insert({name: "Monkey", ration: [{banana: 15}, {water: 10}, {nuts: 1}], holidays: [2], staff: {like: ["Anna"], dislike: "Petrovich"}}); db.zoo.insert({name: "Panda", ration: [{bamboo: 15}, {dumplings: 50}, {water: 3}], staff: {like: ["Petrovich", "Mihalich", "Maria", "Anna"]}});
      
      





名前フィールドは名前を格納し、 配給フィールドは獣が毎日必要とする食べ物の量と種類を格納するオブジェクトの配列です、 休日は獣が休んで訪問者に見せない日、 スタッフです。彼が好きな世話人です)、 staff.dislike-好きではありません。



動物園の監督が誰が呼ばれたかを忘れないように、動物の名前だけで簡単な選択から始めましょう。 ここではすべてが簡単です:



 db.zoo.aggregate({$project: {name: 1}});
      
      





 { "result" : [ { "_id" : ObjectId("4f58b7f627f86b11258dc70c"), "name" : "Lion" }, { "_id" : ObjectId("4f58b86027f86b11258dc70d"), "name" : "Tiger" }, { "_id" : ObjectId("4f58b90c27f86b11258dc70e"), "name" : "Monkey" }, { "_id" : ObjectId("4f58b98727f86b11258dc70f"), "name" : "Panda" } ], "ok" : 1 }
      
      







どのような動物に戦闘機が必要ですか?




捕食者を恐れる必要があります。 捕食者とは、食事に肉が入っている人のことです。 それらを見つけましょう。 まず、ストリームをフィルター処理し、ドキュメント内の2つのフィールド(名前と配給分)のみを選択します。



 db.zoo.aggregate({$project: {name: 1, _id: 0, ration: 1}});
      
      





 { "result" : [ { "name" : "Lion", "ration" : [ { "meat" : 20 }, { "fish" : 1 }, { "water" : 30 } ] }, { "name" : "Tiger", "ration" : [ { "meat" : 15 }, { "water" : 25 } ] }, { "name" : "Monkey", "ration" : [ { "banana" : 15 }, { "water" : 10 }, { "nuts" : 1 } ] }, { "name" : "Panda", "ration" : [ { "bamboo" : 15 }, { "dumplings" : 50 }, { "water" : 3 } ] } ], "ok" : 1 }
      
      







次に、配給配列をメイン配列の要素に展開します。



 db.zoo.aggregate({$project: {name: 1, _id: 0, ration: 1}}, {$unwind: "$ration"});
      
      





 { "result" : [ { "name" : "Lion", "ration" : { "meat" : 20 } }, { "name" : "Lion", "ration" : { "fish" : 1 } }, { "name" : "Lion", "ration" : { "water" : 30 } }, { "name" : "Tiger", "ration" : { "meat" : 15 } }, { "name" : "Tiger", "ration" : { "water" : 25 } }, { "name" : "Monkey", "ration" : { "banana" : 15 } }, { "name" : "Monkey", "ration" : { "water" : 10 } }, { "name" : "Monkey", "ration" : { "nuts" : 1 } }, { "name" : "Panda", "ration" : { "bamboo" : 15 } }, { "name" : "Panda", "ration" : { "dumplings" : 50 } }, { "name" : "Panda", "ration" : { "water" : 3 } } ], "ok" : 1 }
      
      







次に、ration.meatフィールドがあるフィールドに対してのみ選択をフィルタリングします



 db.zoo.aggregate({$project: {name: 1, _id: 0, ration: 1}}, {$unwind: "$ration"}, {$match: {"ration.meat": {$exists: true}}});
      
      





 { "result" : [ { "name" : "Lion", "ration" : { "meat" : 20 } }, { "name" : "Tiger", "ration" : { "meat" : 15 } } ], "ok" : 1 }
      
      







そして最後の結論は捕食者の名前だけです



 db.zoo.aggregate({$project: {name: 1, _id: 0, ration: 1}}, {$unwind: "$ration"}, {$match: {"ration.meat": {$exists: true}}}, {$project: {name: 1, _id: 0}});
      
      





 { "result" : [ { "name" : "Lion" }, { "name" : "Tiger" } ], "ok" : 1 }
      
      







少なくとも1匹の獣は何日休みますか?




これを行うには、休日の配列を動物の配列全体に「階層化」します(パンダは、通常どおり、誰でもいつでもアクセスできます)。



 db.zoo.aggregate({$project: {name: 1, holidays: 1}}, {$unwind: "$holidays"});
      
      





 { "result" : [ { "_id" : ObjectId("4f58b7f627f86b11258dc70c"), "name" : "Lion", "holidays" : 1 }, { "_id" : ObjectId("4f58b7f627f86b11258dc70c"), "name" : "Lion", "holidays" : 4 }, { "_id" : ObjectId("4f58b86027f86b11258dc70d"), "name" : "Tiger", "holidays" : 6 }, { "_id" : ObjectId("4f58b90c27f86b11258dc70e"), "name" : "Monkey", "holidays" : 2 }, { "_id" : ObjectId("4f58b98727f86b11258dc70f"), "name" : "Panda" } ], "ok" : 1 }
      
      







そして、holidaysフィールドが-1より大きい数値(まあ、0の方が便利な場合)のみを除外します



 db.zoo.aggregate({$project: {name: 1, holidays: 1}}, {$unwind: "$holidays"},{$match: {holidays : {$gt: -1}}});
      
      





 { "result" : [ { "_id" : ObjectId("4f58b7f627f86b11258dc70c"), "name" : "Lion", "holidays" : 1 }, { "_id" : ObjectId("4f58b7f627f86b11258dc70c"), "name" : "Lion", "holidays" : 4 }, { "_id" : ObjectId("4f58b86027f86b11258dc70d"), "name" : "Tiger", "holidays" : 6 }, { "_id" : ObjectId("4f58b90c27f86b11258dc70e"), "name" : "Monkey", "holidays" : 2 } ], "ok" : 1 }
      
      







不要なものはすべて削除します。



 db.zoo.aggregate({$project: {name: 1, holidays: 1}}, {$unwind: "$holidays"},{$match: {holidays : {$gt: -1}}}, {$project: {holidays: 1, _id: 0}});
      
      





 { "result" : [ { "holidays" : 1 }, { "holidays" : 4 }, { "holidays" : 6 }, { "holidays" : 2 } ], "ok" : 1 }
      
      







1日に購入する必要のある製品の数。




最も興味深いのは、私の意見では、タスクです。 それを実装するには、$プロジェクトがフィールドを作成し、 meatプロパティの値を使用してmeatフィールドを作成できることを忘れないでください。



 db.zoo.aggregate({$project: {ration: 1, _id: 0}}, {$unwind: "$ration"}, {$project: {ration: 1, meat: "$ration.meat", _id: 0}});
      
      







このフィールドが動物の食事のプロパティにない場合、作成されません。 サンプルパーツの例を次に示します。



 { "result" : [ { "ration" : { "meat" : 20 }, "meat" : 20 }, { "ration" : { "fish" : 1 } }, { "ration" : { "water" : 30 } }, ... }
      
      







すべての種類の食品に対してこれを行い、配給オブジェクト自体の出力を削除します。



 db.zoo.aggregate({$project: {ration: 1}}, {$unwind: "$ration"}, {$project: {ration: 0, _id: 0, meat: "$ration.meat", fish: "$ration.fish", water: "$ration.water", banana: "$ration.banana", bamboo: "$ration.bamboo", nuts: "$ration.nuts", dumplings: "$ration.dumplings", _id: 0}});
      
      







結果として



 { "result" : [ { "_id" : ObjectId("4f58e58227f86b11258dc713"), "meat" : 20 }, { "_id" : ObjectId("4f58e58227f86b11258dc713"), "fish" : 1 }, { "_id" : ObjectId("4f58e58227f86b11258dc713"), "water" : 30 }, { "_id" : ObjectId("4f58e5e127f86b11258dc714"), "meat" : 15 }, { "_id" : ObjectId("4f58e5e127f86b11258dc714"), "water" : 25 }, { "_id" : ObjectId("4f58e60027f86b11258dc715"), "banana" : 15 }, { "_id" : ObjectId("4f58e60027f86b11258dc715"), "water" : 10 }, { "_id" : ObjectId("4f58e60027f86b11258dc715"), "nuts" : 1 }, { "_id" : ObjectId("4f58e64a27f86b11258dc716"), "bamboo" : 15 }, { "_id" : ObjectId("4f58e64a27f86b11258dc716"), "dumplings" : 50 }, { "_id" : ObjectId("4f58e64a27f86b11258dc716"), "water" : 3 } ], "ok" : 1 }
      
      







$ group関数を使用してこの全体を追加/グループ化するだけです。 グループ化の_idフィールドの指定はここでは必須ですが、基本的には必要ないので、なんらかのナンセンスにしてください。 食品の種類ごとに、適切なフィールドを作成して、各動物の個々の食事を要約します。



 db.zoo.aggregate({$project: {ration: 1}}, {$unwind: "$ration"}, {$project: {ration: 0, _id: 0, meat: "$ration.meat", fish: "$ration.fish", water: "$ration.water", banana: "$ration.banana", bamboo: "$ration.bamboo", nuts: "$ration.nuts", dumplings: "$ration.dumplings"}}, {$group: {_id: "s", sum_meat: {$sum: "$meat"}, sum_fish: {$sum: "$fish"}, sum_water: {$sum: "$water"}, sum_banana: {$sum: "$banana"}, sum_nuts: {$sum: "$nuts"}, sum_bamboo: {$sum: "$bamboo"}, sum_dumplings: {$sum: "$dumplings"}}});
      
      





 { "result" : [ { "_id" : "s", "sum_meat" : 35, "sum_fish" : 1, "sum_water" : 68, "sum_banana" : 15, "sum_nuts" : 1, "sum_bamboo" : 15, "sum_dumplings" : 50 } ], "ok" : 1 }
      
      







好きな世話人




フィールドでフィルターし、staff.like配列を巻き戻します。



 db.zoo.aggregate({$project: {name: 1, _id: 0, "staff.like": 1}}, {$unwind: "$staff.like"});
      
      





$プロジェクトは、フィールドを1レベル上げることができることを思い出します。



 db.zoo.aggregate({$project: {name: 1, _id: 0, "staff.like": 1}}, {$unwind: "$staff.like"}, {$project: {_id: 0, name: "$staff.like"}});
      
      





そこで、少なくとも誰かが好きで、誰かが2匹の動物が好きなすべての管理人を選択しました。そして、彼はサンプルに2回現れます。



 { "result" : [ { "name" : "Petrovich" }, { "name" : "Mihalich" }, { "name" : "Petrovich" }, { "name" : "Maria" }, { "name" : "Anna" }, { "name" : "Petrovich" }, { "name" : "Mihalich" }, { "name" : "Maria" }, { "name" : "Anna" } ], "ok" : 1 }
      
      







次に、これらのフィールドを合計する必要があります。 しかし、これを行うのはそれほど簡単ではありません。合計するフィールドがないため、既知のチップでこのフィールドを作成します。



 db.zoo.aggregate({$project: {name: 1, _id: 0, "staff.like": 1}}, {$unwind: "$staff.like"}, {$project: {_id: 0, name: "$staff.like", count: {$add: [1]}}});
      
      





その結果、値1の別のカウントフィールドが各オブジェクトに追加されます。



 db.zoo.aggregate({$project: {name: 1, _id: 0, "staff.like": 1}}, {$unwind: "$staff.like"}, {$project: {_id: 0, name: "$staff.like", count: {$add: [1]}}}, {$group: {_id: "$name", num: {$sum: "$count"}}});
      
      





出力をソートして、最初の要素に制限します



 db.zoo.aggregate({$project: {name: 1, _id: 0, "staff.like": 1}}, {$unwind: "$staff.like"}, {$project: {_id: 0, name: "$staff.like", count: {$add: [1]}}}, {$group: {_id: "$name", num: {$sum: "$count"}}}, {$sort: {num: -1}}, {$limit: 1});
      
      





そして、次のものが得られます。



 { "result" : [ { "_id" : "Petrovich", "num" : 3 } ], "ok" : 1 }
      
      







それだけです。 興味のある方のために、このトピックに関する英語の2つの簡単なレポートがあります: 1つと 2つ



正直なところ、MongoDBは本当に気に入っていますが、異なるデータを格納するためにプロジェクトの一部でのみ使用しました。 私にとって同じMap / Reduceはいつも怖くて理解できないものですが、新しいデータ集約のおかげでJavaScriptを部分的に除外することができます。 。



PSバージョン2.1はまだかなり粗雑であることに注意する価値があります。 アサーションが失敗すると、あらゆる種類の例外が常に発生します。 しかし、2.2では最終的に安定し、クールになると思います。



All Articles