標籤:

學習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" ] }>

我們發現只要包含蘋果的數組都能被查詢出來。如果要通過多個元素來匹配數組,就需要條件操作符"$all",比如我們要查詢既賣apple又賣banana的水果店:

> 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的處理方式是完全相同的匹配,即順序與數量都要一致,上述中第一條文檔和查詢條件的順序不一致,第三條文檔比查詢條件文檔多一個元素,都沒有被匹配成功!

對於數組的匹配,還有一種形式是精確指定數組中某個位置的元素匹配,我們前面提到,數組中的索引可以作為鍵使用,如我們要匹配水果店售第二種水果是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開始,我們匹配第二種水果就用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函數的第二個參數用於查詢返回哪些鍵,他還可以控制查詢返回數組的一個子數組,如下例:我只想查詢水果店售賣說過數組的前兩個:

> 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],即跳過前兩個,截取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" }>

如果第二個參數中有個鍵使用了條件操作符"$slice",則默認查詢會返回所有的鍵,如果此時你要忽略哪些鍵,可以手動指明!如:

> db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}, "name":0, "_id":0});{ "fruits" : [ "strawberry", "banana" ] }{ "fruits" : [ ] }{ "fruits" : [ "banana" ] }>

【查詢內嵌文檔】

查詢文檔有兩種方式,一種是完全匹查詢,另一種是針對鍵值對查詢!內嵌文檔的完全匹配查詢和數組的完全匹配查詢一樣,內嵌文檔內鍵值對的數量,順序都必須一致才會匹配,如下例:

> 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" : "just so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] }>

我們想要查詢評論中有叫「joe」並且其給出的分數超過5分的blog文檔,但我們利用「點表示法」直接寫是有問題的,因為這條文檔有兩條評論,一條的作者名字叫「joe」但分數只有3,一條作者名字叫「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" : "just so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] }>

這樣做,結果是正確的!利用條件操作符「$elemMatch」可以組合一組條件,並且還能達到的「點表示法」的模糊查詢的效果!

【$where】

上面提到的所有的鍵值對的查詢方式,我們也可以看出,已經很強大了!但如果實際中真的遇到一種情況無法用上述方式實現時,不用慌,MongoDB為我們提供了終極武器:"$where",用他可以執行任意JavaScript作為查詢的一部分!最典型的應用:一個文檔,如果有兩個鍵的值相等,就選出來,否則不選:

> 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」條件操作符也是作為外層文檔的鍵使用,昨天說「$or」條件操作符是被作為外層文檔的鍵使用。其餘目前遇到的條件操作符都是被作為內層文檔的鍵使用!


推薦閱讀:

4 · 學習 |和優秀的人在一起真的很重要!
我所見過的最全的學習網址
9張圖助你提高成績!(孩子必知的學習技巧)
【書法學習】一周硬筆速成動態圖教學 *
曳步舞太空舞滑步學習乾貨,看了應該對你幫助!

TAG:學習 | MongoDB |