MongoDBクエリの配列、埋め込みドキュメント、および$where


【配列】
配列を検索するのは簡単です.配列については、配列の各要素がこのキー値対キーの有効値であることを理解できます.次の例のように、appleを販売している果物店を検索します.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("502251a309248743250688e1"), "name" : "good fruit", "fruits" : [ "banana", "pear", "orange" ] }  
    { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] }  
    > db.fruitshop.find({"fruits":"apple"});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] }  
    >

アップルを含む配列さえあれば検索できることが分かった.複数の要素で配列をマッチングするには、appleとbananaの両方を販売する果物店を検索するなど、条件オペレータ「$all」が必要です.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("502251a309248743250688e1"), "name" : "good fruit", "fruits" : [ "banana", "pear", "orange" ] }  
    { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] }  
    > db.fruitshop.find({"fruits":{"$all":["apple","banana"]}});  
    { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] }  
    >

「$all」を使用すると、配列内の要素の順序は要求されず、すべての配列がクエリーされる限りです.配列クエリは、クエリ条件ドキュメント内のキー値ペアの値も配列である正確な一致を使用することもできます.たとえば、次のようにします.
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] }  
    > db.fruitshop.find({"fruits":["apple","orange","pear"]});  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    >

正確に一致する場合、MongoDBの処理方法は完全に同じ一致であり、すなわち順序と数量が一致し、上記のうち第1のドキュメントとクエリー条件の順序が一致せず、第3のドキュメントはクエリー条件ドキュメントより1つの要素が多く、一致に成功しなかった.
配列のマッチングについては、配列内の位置を正確に指定する要素マッチングの形式もあります.前述したように、配列内のインデックスはキーとして使用できます.例えば、果物店で2番目に販売されている果物がorangeの果物店であることをマッチングします.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] }  
    > db.fruitshop.find({"fruits.1":"orange"});  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] }  
    >

配列インデックスは0から2番目の果物にマッチしてfuritsを使います.1をキーとして使用します.
「$size」条件オペレータは、3種類の果物を販売する果物店を検索するなど、特定の長さの配列を検索するために使用できます.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] }  
    > db.fruitshop.find({"fruits":{"$size":3}});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] }  
    { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] }  
    >

ただし、条件オペレータ「$size」は、他のオペレータと「$gt」などと連用することはできません.これは、このオペレータの欠陥です.このオペレータを使用すると、ある長さの配列を正確にクエリーするしかありません.実際に配列をクエリーする場合は、その長さの範囲でクエリーする必要があります.ここで推奨する方法は、このドキュメントに配列のサイズを記録する「size」キーを追加し、配列を「$push」操作しながら、この「size」キー値を1加算することです.次のようになります.
    > db.fruitshop.find({"name":"big fruit"});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry" ], "name" : "big fruit", "size" : 4 }  
    > db.fruitshop.update({"name":"big fruit"},  
    ... {"$push":{"fruits":"banana"}, "$inc":{"size":1}}, false, false);  
    > db.fruitshop.find({"name":"big fruit"});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit", "size" : 5 }  
    >

しかし、この方法はモディファイヤ「$addToSet」と組み合わせられません.この要素が配列に追加されたかどうかを判断できないからです.
find関数の2番目のパラメータは、どのキーを返すかを調べるために使用されます.彼は、次の例のように、クエリーが配列を返すサブ配列を制御することもできます.私は果物屋で配列を売った最初の2つだけを調べたいです.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit" }  
    { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange", "pear" ], "name" : "fruit king" }  
    { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange", "pear", "banana" ], "name" : "good fruit" }  
    > db.fruitshop.find({}, {"fruits":{"$slice":2}});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear" ], "name" : "big fruit" }  
    { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange" ], "name" : "fruit king" }  
    { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange" ], "name" : "good fruit" }  
    >

「$slice」も後ろから切り取ることができ、マイナス数でいいです.例えば、-1は最後の切り取りを示します.また、[2,3]のような中間部分を切り取ることもできます.つまり、前の2つをスキップして、3つを切り取って、残りが3つ未満であれば、すべて返します.
    > db.fruitshop.find();  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit" }  
    { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange", "pear" ], "name" : "fruit king" }  
    { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange", "pear", "banana" ], "name" : "good fruit" }  
    > db.fruitshop.find({}, {"fruits":{"$slice":-1}});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "banana" ], "name" : "big fruit" }  
    { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "pear" ], "name" : "fruit king" }  
    { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "banana" ], "name" : "good fruit" }  
    > db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}});  
    { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "strawberry", "banana" ], "name" : "big fruit" }  
    { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ ], "name" : "fruit king" }  
    { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "banana" ], "name" : "good fruit" }  
    >

2番目のパラメータで条件オペレータ'$slice'を使用するキーがある場合、デフォルトのクエリはすべてのキーを返します.この場合、どのキーを無視するかは手動で指定できます.次のようになります.
    > db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}, "name":0, "_id":0});  
    { "fruits" : [ "strawberry", "banana" ] }  
    { "fruits" : [ ] }  
    { "fruits" : [ "banana" ] }  
    >

【インラインドキュメント】
ドキュメントをクエリーするには、完全一致クエリーとキー値対クエリーの2つの方法があります.埋め込みドキュメントの完全一致クエリーは、配列の完全一致クエリーと同様に、埋め込みドキュメントのキー値ペアの数は、次の例で一致する順序で一致する必要があります.
    > db.staff.find();  
    { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 }  
    { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 }  
    { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 }  
    > db.staff.find({"name":{"first":"joe","middle":"bush"}});  
    { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 }  
    >

埋め込みドキュメントの特定のキー値ペアに対するクエリーが最も一般的です.ポイント表現によって、埋め込まれたドキュメントのキーを正確に表示します.
    > db.staff.find();  
    { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 }  
    { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 }  
    { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 }  
    > db.staff.find({"name.first":"joe", "name.middle":"bush"});  
    { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 }  
    { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 }  
    { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 }  
    >

このようにクエリーすると、すべての有効なドキュメントがクエリーされます.ポイント表示法により、埋め込みドキュメントの内部に深く入り込むキーを表すことができます.「点表現」を使用して埋め込まれたドキュメントをクエリーします.これにより、ドキュメントを挿入するときに「.」!
埋め込みドキュメントが複雑になると、キーの値が埋め込みドキュメントの配列など、埋め込みドキュメントのマッチングには次の例のテクニックが必要です.
    > db.blogs.findOne();  
    {  
            "_id" : ObjectId("502262ab09248743250688ea"),  
            "content" : ".....",  
            "comment" : [  
                    {  
                            "author" : "joe",  
                            "score" : 3,  
                            "comment" : "just so so!"  
                    },  
                    {  
                            "author" : "jimmy",  
                            "score" : 5,  
                            "comment" : "cool! good!"  
                    }  
            ]  
    }  
    > db.blogs.find({"comment.author":"joe", "comment.score":{"$gte":5}});  
    { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [      {       "author" : "joe",       "score" : 3,    "comment" : "j  
    ust so so!" },  {       "author" : "jimmy",     "score" : 5,    "comment" : "cool! good!" } ] }  
    >

私たちはコメントの中に「joe」という点数が5点を超えるblogドキュメントを検索したいのですが、「点表現法」を利用して直接書くのは問題があります.このドキュメントには2つのコメントがあり、1つの作者の名前は「joe」ですが、点数は3つしかありません.1つの作者の名前は「jimmy」ですが、点数は5をあげました.つまり、このクエリー条件は配列内の異なるドキュメントと一致しています.これは私たちが望んでいるものではありません.ここでは、各キーを単一で示すのではなく、条件オペレータ「$elemMatch」を使用して条件のセットを使用します.彼は、配列内の単一のドキュメントのマッチングに条件のセットを限定することができます.
    > db.blogs.findOne();  
    {  
            "_id" : ObjectId("502262ab09248743250688ea"),  
            "content" : ".....",  
            "comment" : [  
                    {  
                            "author" : "joe",  
                            "score" : 3,  
                            "comment" : "just so so!"  
                    },  
                    {  
                            "author" : "jimmy",  
                            "score" : 5,  
                            "comment" : "cool! good!"  
                    }  
            ]  
    }  
    > db.blogs.find({"comment":{"$elemMatch":{"author":"joe", "score":{"$gte":5}}}});  
    > db.blogs.find({"comment":{"$elemMatch":{"author":"joe", "score":{"$gte":3}}}});  
    { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [      {       "author" : "joe",       "score" : 3,    "comment" : "j  
    ust so so!" },  {       "author" : "jimmy",     "score" : 5,    "comment" : "cool! good!" } ] }  
    >

このようにして、結果は正しいです!条件オペレータ「$elemMatch」を使用すると、条件のセットを組み合わせることができ、ポイント表現のあいまいなクエリーの効果も得られます.
【$where】
上記のすべてのキー値ペアのクエリー方法は、すでに強力であることもわかります.しかし、実際にこのような方法で実現できない状況に遭遇した場合、慌てずにMongoDBは私たちに究極の武器を提供してくれました:「$where」、彼が任意のJavaScriptをクエリーの一部として実行することができます!最も一般的なアプリケーション:1つのドキュメントで、2つのキーの値が等しい場合は選択します.そうでない場合は選択しません.
    > db.fruitprice.find();  
    { "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 }  
    { "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }  
    > db.fruitprice.find({"$where":function () {  
    ... for(var current in this){  
    ...     for(var other in this){  
    ...         if(current != other && this[current] == this[other]){  
    ...             return true;  
    ...         }  
    ...     }  
    ... }  
    ... return false;  
    ... }});  
    { "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 }  
    >

「$where」を使用するとjavascript関数が書かれていることがわかります.MongoDBはクエリー時に各ドキュメントをjavascriptオブジェクトに変換し、この関数に捨てて実行し、結果を返すことで一致するかどうかを判断します.実際の使用では、パフォーマンスが悪いため、$where条件オペレータの使用は避けましょう.実行中に、各ファイルをjavascriptオブジェクトに変換する必要があります.避けられない場合は、できるだけfind({「other」:「......」,...,「$where」:「}」)は、「$where」を最後にして結果として最適化し、通常のクエリーを前のフィルタ条件として使用します.これにより、パフォーマンスの損失を減らすことができます.
$where補足:
参照先:https://docs.mongodb.com/manual/reference/operator/query/where/
定義:“   Use the $where operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system. The $where provides greater flexibility, but requires that the database processes the JavaScript expression or function for each document in the collection. Reference the document in the JavaScript expression or function using either this or obj .    ”    
制限:
  • Do not use global variables.   
  • $where evaluates JavaScript and cannot take advantage of indexes. Therefore, query performance improves when you express your query using the standard MongoDB operators (e.g., $gt, $in).   
  • In general, you should use $where only when you can’t express your query using another operator. If you must use $where, try to include at least one other standard query operator to filter the result set. Using $where alone requires a collection scan.