上篇主要介紹了一些基本的查詢條件操作符的使用,主要針對的是一些單值,我們這次來講講如何查詢文檔內(nèi)的數(shù)組和嵌入文檔,并講一下復(fù)雜查詢"$where"。
【查詢數(shù)組】
查詢數(shù)組很容易,對于數(shù)組,我們可以這樣理解:數(shù)組中每一個元素都是這個鍵值對鍵的一個有效值,如下面的例子:我們要查詢出售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" ] }>
我們發(fā)現(xiàn)只要包含蘋果的數(shù)組都能被查詢出來。如果要通過多個元素來匹配數(shù)組,就需要條件操作符"$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”對數(shù)組內(nèi)元素的順序沒有要求,只要全部包含的數(shù)組都能查詢出來。數(shù)組查詢也可以使用精確匹配的方式,即查詢條件文檔中鍵值對的值也是數(shù)組,如:
{ "_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的處理方式是完全相同的匹配,即順序與數(shù)量都要一致,上述中第一條文檔和查詢條件的順序不一致,第三條文檔比查詢條件文檔多一個元素,都沒有被匹配成功!
對于數(shù)組的匹配,還有一種形式是精確指定數(shù)組中某個位置的元素匹配,我們前面提到,數(shù)組中的索引可以作為鍵使用,如我們要匹配水果店售第二種水果是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" ] }>
數(shù)組索引從0開始,我們匹配第二種水果就用furits.1作為鍵。
"$size"條件操作符,可以用來查詢特定長度的數(shù)組的,如我們要查詢賣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”等,這是這個操作符的一個缺陷。使用這個操作符我們只能精確查詢某個長度的數(shù)組。如果實際中,在查詢某個數(shù)組時,需要按其長度范圍進(jìn)行查詢,這里推薦的做法是:在這個文檔中額外增加一個“size”鍵,專門記錄其中數(shù)組的大小,在對數(shù)組進(jìn)行"$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"沒法配合使用,因為你無法判斷這個元素是否添加到了數(shù)組中!
上篇提到了,find函數(shù)的第二個參數(shù)用于查詢返回哪些鍵,他還可以控制查詢返回數(shù)組的一個子數(shù)組,如下例:我只想查詢水果店售賣說過數(shù)組的前兩個:
> 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”也可以從后面截取,用復(fù)數(shù)即可,如-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" }>
如果第二個參數(shù)中有個鍵使用了條件操作符"$slice",則默認(rèn)查詢會返回所有的鍵,如果此時你要忽略哪些鍵,可以手動指明!如:
> db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}, "name":0, "_id":0});{ "fruits" : [ "strawberry", "banana" ] }{ "fruits" : [ ] }{ "fruits" : [ "banana" ] }>
【查詢內(nèi)嵌文檔】
查詢文檔有兩種方式,一種是完全匹查詢,另一種是針對鍵值對查詢!內(nèi)嵌文檔的完全匹配查詢和數(shù)組的完全匹配查詢一樣,內(nèi)嵌文檔內(nèi)鍵值對的數(shù)量,順序都必須一致才會匹配,如下例:
> 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 }>
針對內(nèi)嵌文檔特定鍵值對的查詢是最常用的!通過點表示法來精確表示內(nèi)嵌文檔的鍵:
> 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 }>
我們看,這樣查詢,所有有效文檔均被查詢到了!通過點表示法,可以表示深入到內(nèi)嵌文檔內(nèi)部的鍵!利用“點表示法”來查詢內(nèi)嵌文檔,這也約束了在插入文檔時,任何鍵都不能包含“.” !!
當(dāng)內(nèi)嵌文檔變得復(fù)雜后,如鍵的值為內(nèi)嵌文檔的數(shù)組,這種內(nèi)嵌文檔的匹配需要一些技巧,如下例:
> 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”并且其給出的分?jǐn)?shù)超過5分的blog文檔,但我們利用“點表示法”直接寫是有問題的,因為這條文檔有兩條評論,一條的作者名字叫“joe”但分?jǐn)?shù)只有3,一條作者名字叫“jimmy”,分?jǐn)?shù)卻給了5!也就是這條查詢條件和數(shù)組中不同的文檔進(jìn)行了匹配!這不是我們想要的,我們這里是要使用一組條件而不是單個指明每個鍵,使用條件操作符“$elemMatch”即可!他能將一組條件限定到數(shù)組中單條文檔的匹配上:
> 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!" } ] }>
這樣做,結(jié)果是正確的!利用條件操作符“$elemMatch”可以組合一組條件,并且還能達(dá)到的“點表示法”的模糊查詢的效果!
【$where】
上面提到的所有的鍵值對的查詢方式,我們也可以看出,已經(jīng)很強(qiáng)大了!但如果實際中真的遇到一種情況無法用上述方式實現(xiàn)時,不用慌,MongoDB為我們提供了終極武器:"$where",用他可以執(zhí)行任意JavaScript作為查詢的一部分!最典型的應(yīng)用:一個文檔,如果有兩個鍵的值相等,就選出來,否則不選:
> 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函數(shù),MongoDB在查詢時,會將每個文檔轉(zhuǎn)換成一個javascript對象,然后扔到這個函數(shù)中去執(zhí)行,通過返回結(jié)果來判斷其是否匹配!在實際使用中,盡量避免使用”$where" 條件操作符,因為其性能很差!在執(zhí)行過程中,需要把每個檔案轉(zhuǎn)化為javascript對象!如果不可避免,則盡量這樣寫:find({”other“:”......“,......,“$where”:""}),即將"$where"放最后,作為結(jié)果調(diào)優(yōu),讓常規(guī)查詢作為前置過濾條件!這樣能減少一些性能損失!
我們這里還可以發(fā)現(xiàn),“$where”條件操作符也是作為外層文檔的鍵使用,昨天說“$or”條件操作符是被作為外層文檔的鍵使用。其余目前遇到的條件操作符都是被作為內(nèi)層文檔的鍵使用!