MongoDBの$ unwindオペレーターとは何ですか?


103

今日はMongoDBでの初日なので、私と一緒に楽になってください:)

$unwindオペレーターが理解できません。おそらく英語が母国語ではないためです。

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

プロジェクトオペレーターは私が理解できるものだと思います(のようSELECTですね)。しかし、その後、$unwind(引用)すべてのソースドキュメント内の巻き戻された配列のすべてのメンバーに対して1つのドキュメントを返します

これはJOIN?そうならば、どのように結果$project(と_idauthortitleおよびtagsフィールド)と比較することができるtags配列?

:私はMongoDBのWebサイトから例を挙げましたが、tags配列の構造がわかりません。タグ名の単純な配列だと思います。

回答:


235

まず、MongoDBへようこそ!

覚えておくべきことは、MongoDBはデータストレージに「NoSQL」アプローチを採用しているため、選択、結合などの考えを頭から消し去ってしまうことです。データを保存する方法は、ドキュメントとコレクションの形式であり、これにより、保存場所からデータを動的に追加および取得できます。

そうは言っても、$ unwindパラメーターの背後にある概念を理解するには、まず、引用しようとしているユースケースの内容を理解する必要があります。mongodb.orgのサンプルドキュメントは次のとおりです。

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

タグが実際には3つの項目の配列であることに注意してください。この場合は、「楽しい」、「良い」、「楽しい」です。

$ unwindの機能は、要素ごとにドキュメントをはがして、その結果のドキュメントを返すことです。これを古典的なアプローチで考えると、「tags配列内の各アイテムに対して、そのアイテムのみを含むドキュメントを返す」と同等です。

したがって、以下を実行した結果:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

次のドキュメントを返します。

{
     "result" : [
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "good"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             }
     ],
     "OK" : 1
}

結果の配列で変更されるのは、タグの値で返されるものだけです。これがどのように機能するかについての追加のリファレンスが必要な場合は、ここにリンクを含めまし。これがお役に立てば幸いです。私がこれまでに出会った中で最高のNoSQLシステムの1つへの進出に成功してください。


44

$unwind 配列要素ごとに1回、パイプライン内の各ドキュメントを複製します。

あなたの入力パイプラインは、2つの要素を持つ一品ドキュメントが含まれているのであればtags{$unwind: '$tags'}以外は同じです2品ドキュメントするパイプラインを変換しますtagsフィールド。最初のドキュメントにtagsは、元のドキュメントの配列の最初の要素が含まれ、2番目のドキュメントにtagsは2番目の要素が含まれます。


22

例でそれを理解しましょう

これは会社のドキュメントがどのように見えるかです:

元の書類

を使用$unwindすると、配列値フィールドを持つドキュメントを入力として取得し、配列内の要素ごとに1つの出力ドキュメントが存在するような出力ドキュメントを生成できます。ソース

$ unwindステージ

それでは、会社の例に戻り、アンワインドステージの使用方法を見てみましょう。このクエリ:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

金額と年の両方の配列を持つドキュメントを生成します。

プロジェクト出力

資金調達ラウンドアレイ内のすべての要素の調達額と資金調達年にアクセスしているためです。これを修正するには、この集約パイプラインのプロジェクトステージの前にアンワインドステージを含め、これをパラメーター化して、unwind、資金調達ラウンドアレイにし。


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $unwind: "$funding_rounds" },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

unwindは、入力として受け取るよりも多くのドキュメントを次のステージに出力する効果があります

funding_rounds配列を見ると、それぞれfunding_roundsraised_amountfunded_yearフィールドがあることがわかります。したがって、配列のunwind要素であるドキュメントごとにfunding_rounds、出力ドキュメントが生成されます。さて、この例では、値はstringsです。ただし、内の配列内の要素の値のタイプに関係なくunwindこれらの値のそれぞれについて出力ドキュメントが生成され、問題のフィールドにはその要素のみが含まれます。の場合funding_rounds、その要素はfunding_roundsprojectステージに渡されるすべてのドキュメントの値として、これらのドキュメントの1つになります。これを実行した結果は、amountとを取得することyearです。各企業の資金調達ラウンドごとに 1つ私たちのコレクションで。これは、私たちの一致が多くの会社文書を生成し、それらの会社文書のそれぞれが多くの文書をもたらすことを意味します。すべての会社のドキュメント内の資金調達ラウンドごとに1つ。ステージunwindから渡されたドキュメントを使用してこの操作を実行しmatchます。そして、すべての会社のこれらすべてのドキュメントがprojectステージに渡されます。

出力を巻き戻します

したがって、(クエリの例のように)資金提供者がGreylockであったすべてのドキュメントは、フィルターに一致するすべての会社の資金調達ラウンドの数に等しい数のドキュメントに分割されます$match: {"funding_rounds.investments.financial_org.permalink": "greylock" }。そして、それらの結果として得られるドキュメントはそれぞれ、に渡されますproject。これで、unwind入力として受け取るすべてのドキュメントの正確なコピーが作成されます。1つの例外を除いて、すべてのフィールドは同じキーと値を持ちます。つまり、funding_roundsフィールドはfunding_roundsドキュメントの配列ではなく、単一のドキュメントである値を持ちます。これは、個別の資金調達ラウンドです。したがって、4つの資金調達ラウンドがある企業は、ドキュメントをunwind作成することになり 4ます。すべてのフィールドは正確なコピーですが、funding_roundsこれらのコピーのそれぞれの配列ではなく、現在処理しfunding_roundsている会社のドキュメントの配列の個々の要素になりunwindます。したがって、、フィールドとフィールドします。そのため、各企業の複数のドキュメントを受け取りますunwind入力として受け取るよりも多くのドキュメントを次のステージに出力する効果があります。つまり、私たちのprojectステージfunding_roundsは、配列ではなく、代わりにを持つネストされたドキュメントであるフィールドを取得することになります。raised_amountfunded_yearprojectmatch、フィルターを使用するドキュメントを個別に処理してを識別でき、各会社の各資金調達ラウンドの個別の金額と年を


2
同じドキュメントを使用する方が良いでしょう。
Jeb50 2017

1
$ unwindの最初の使用例として、ネストされたセットのか​​なり複雑なネストされたセットがありました。mongo docsとstackowerflowの間を行き来して、あなたの答えはついに$ projectと$ unwindをよりよく理解するのに役立ちました。@Zameerに感謝!
7

3

mongodb公式ドキュメントによると:

$ unwind入力ドキュメントから配列フィールドを分解して、各要素のドキュメントを出力します。各出力ドキュメントは、配列フィールドの値が要素に置き換えられた入力ドキュメントです。

基本的な例による説明:

コレクションインベントリには次のドキュメントがあります。

{ "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] }
{ "_id" : 2, "item" : "EFG", "sizes" : [ ] }
{ "_id" : 3, "item" : "IJK", "sizes": "M" }
{ "_id" : 4, "item" : "LMN" }
{ "_id" : 5, "item" : "XYZ", "sizes" : null }

次の$ unwind操作は同等であり、sizesフィールドの各要素のドキュメントを返します。サイズフィールドが配列に解決されないが、欠落、null、または空の配列ではない場合、$ unwindは非配列オペランドを単一要素配列として扱います。

db.inventory.aggregate( [ { $unwind: "$sizes" } ] )

または

db.inventory.aggregate( [ { $unwind: { path: "$sizes" } } ] 

上記のクエリ出力:

{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

なぜそれが必要なのですか?

$ unwindは、集計の実行中に非常に役立ちます。ソート/検索などのさまざまな操作を実行する前に、複雑な/ネストされたドキュメントを単純なドキュメントに分割します。

$ unwindについてさらに知るには:

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

集計について詳しく知るには:

https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/


2

コレクション内のこのデータを理解するには、以下の例を検討してください

{
        "_id" : 1,
        "shirt" : "Half Sleeve",
        "sizes" : [
                "medium",
                "XL",
                "free"
        ]
}

クエリ-db.test1.aggregate([{$ unwind: "$ sizes"}]);

出力

{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "medium" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "XL" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "free" }

1

RDBMSの方法に関連する方法で説明させてください。これはステートメントです:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

ドキュメント/レコードに適用するには:

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

$プロジェクト/選択は、単にこれらのフィールド/列を返します。

SELECT著者、タイトル、タグFROM記事

次に、Mongoの楽しい部分です。この配列をtags : [ "fun" , "good" , "fun" ]、「タグ」という名前の別の関連テーブル(値が重複しているため、ルックアップ/参照テーブルにすることはできません)と考えてください。SELECTは通常、垂直方向に物を生成するため、「タグ」を巻き戻すと、垂直方向にsplit()がテーブル「タグ」に分割されます。

$ project + $ unwindの最終結果: ここに画像の説明を入力してください

出力をJSONに変換します。

{ "author": "bob", "title": "this is my title", "tags": "fun"},
{ "author": "bob", "title": "this is my title", "tags": "good"},
{ "author": "bob", "title": "this is my title", "tags": "fun"}

Mongoに "_id"フィールドを省略するように指示しなかったため、自動で追加されます。

重要なのは、集計を実行するときにテーブルのようにすることです。


またはそれを考える別の方法はUNION ALLです
Jeb50
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.