MongoDB權威指南(3)- 查詢

jopen 12年前發布 | 37K 次閱讀 MongoDB NoSQL數據庫

1.find方法介紹

在不傳入參數的情況下,find方法缺省使用{}做參數,它匹配所有的document。

我們可以傳入一個查詢document給find方法來限制輸出,如:查找年齡為27的用戶

>  db.users.find({ " age "  :  27 })

一個查詢document里可以包括多個條件,如:查詢年齡為27并且名字叫joe的用戶

>  db.users.find({ " username "  :  " joe " " age "  :  27 })

條件之間都是And關系。

指定查詢返回的內容

有時候我們并不需要返回document里所有的key/value對,我們可以給find(或findOne)方法傳入第二個參數來指定返回哪些key的值。

比如:我們只想要用戶的名字和Email

>  db.users.find({}, { " username "  :  1 " email "  :  1 })
{
        " _id "  : ObjectId( " 4ba0f0dfd22aa494fd523620 " ),
        " username "  :  " joe " ,
        " email "  :  " joe@example.com "
}

"_id"鍵總是會被返回。

如果想指定不想返回的key,如:返回除了fatal_weakness以外的key的值

>  db.users.find({}, { " fatal_weakness "  :  0 })

用這個方法可以把缺省返回的"_id"給排除掉

>  db.users.find({}, { " username "  :  1 " _id "  :  0 })
{
" username "  :  " joe " ,
}

限制

查詢document里邊key的值對數據庫來說必須是個常量,假如collection里邊有兩個key,一個叫int_stock一個叫num_sold,

如果我們想查詢這兩個key相等的document,下邊的查詢是行不通的

>  db.stock.find({ " in_stock "  :  " this.num_sold " })  //  doesn't work

2.查詢條件

"$lt", "$lte", "$gt", 和"$gte"分別對應<,<=,>,>=

如:查詢年齡在18到30之間的用戶

>  db.users.find({ " age "  : { " $gte "  :  18 " $lte "  :  30 }})

“$ne" 不等于,可以用于任意類型的數據

如,查找名字不叫joe的用戶

>  db.users.find({ " username "  : { " $ne "  :  " joe " }})

OR查詢

mongoDB里有兩種使用OR的方法,如果是一個key對應的多個值,可以用"$in",還有個”or"是一種更通用的方法。

如:我們在進行一個抽獎活動,ticket No是725,542,390的三個中獎了,我們可以這樣把這三個document查出來

>  db.raffle.find({ " ticket_no "  : { " $in "  : [ 725 542 390 ]}})

"$in"里邊指定的值可以是不同的數據類型

與"$in"相反的操作就是"$nin”,返回指定的key的值不存在于數組里邊的document,

>  db.raffle.find({ " ticket_no "  : { " $nin "  : [ 725 542 390 ]}})

上邊的查詢返回沒中獎的所有ticket。

如果要在多個key的查詢條件之間建立Or關系就得用"$or",如,查詢彩票號碼為725542390  或者winner為true的document

>  db.raffle.find({ " $or "  : [{ " ticket_no "  : { " $in "  : [ 725 542 390 ]}},{ " winner "  :  true }]})

$not

"$not"可以應用于上邊說的所有的條件操作符上,舉個例子之前我們先看下求余(取模)操作符"$mod","$mod"指定一個整數數組做為key的值,

里邊兩個數字,第一個是對其求余的數字,第二個是余數。比如,我們要找id_num的值對5求余,余數為1的document

>  db.users.find({ " id_num "  : { " $mod "  : [ 5 1 ]}})

現在我們使用$not,如果找id_num對5求余后余數不為1的document

>  db.users.find({ " id_num "  : { " $not "  : { " $mod "  : [ 5 1 ]}}})

規律

回頭再看下前一章的更新修飾符,就會發現同樣是$打頭的符號,它和本章說的查詢條件符號的位置是不一樣的,

在查詢里,"$lt"屬于內層document,而更新的時候"$inc"是外層document的key。

一個key上可以有多個查詢條件,卻不能有多個更新。

查詢年齡為20到30的用戶可以這樣做

>  db.users.find({ " age "  : { " $lt "  :  30 " $gt "  :  20 }})

但是卻沒有下邊這種更新修飾符

{ " $inc "  : { " age "  :  1 },  " $set "  : {age :  40 }}

3.特殊類型查詢

null

null同時匹配自身和不存在的key,

>  db.c.find()
" _id "  : ObjectId( " 4ba0f0dfd22aa494fd523621 " ),  " y "  :  null  }
" _id "  : ObjectId( " 4ba0f0dfd22aa494fd523622 " ),  " y "  :  1  }
" _id "  : ObjectId( " 4ba0f148d22aa494fd523623 " ),  " y "  :  2  }

如果要查找y為null的document

>  db.c.find({ " y "  :  null })
" _id "  : ObjectId( " 4ba0f0dfd22aa494fd523621 " ),  " y "  :  null  }

查找key為null的document時,缺少此key的所有document也會返回

>  db.c.find({ " z "  :  null })
" _id "  : ObjectId( " 4ba0f0dfd22aa494fd523621 " ),  " y "  :  null  }
" _id "  : ObjectId( " 4ba0f0dfd22aa494fd523622 " ),  " y "  :  1  }
" _id "  : ObjectId( " 4ba0f148d22aa494fd523623 " ),  " y "  :  2  }

如果想查找key存在并且為null的document,就要用"$exists"符號,

>  db.c.find({ " z "  : { " $in "  : [ null ],  " $exists "  :  true }})

這個辦法看起來比較笨,沒辦法,我們沒有"$eq"條件符號。

正則表達式

查找名字叫joe,但是忽略大小寫的document

>  db.users.find({ " name "  :  / joe / i})

mongoDB使用Perl Compatible Regular Expression (PCRE)來匹配正則表達式,所有PCRE允許的語法都可以在mongoDB里使用。

查詢數組

查詢數組很簡單,通常情況下,數組的每個元素的值都能看作key的值。

例如,我們有一些水果

>  db.food.insert({ " fruit "  : [ " apple " " banana " " peach " ]})

我們查找水果里有香蕉的ducoment

>  db.food.find({ " fruit "  :  " banana " })

用起來就好像我們有這么一個document在

{ " fruit "  :  " apple " , " fruit "  :  " banana " " fruit "  :  " peach " }

當然,上邊這個document是不合法的。

$all

如果要匹配數組里的多個元素,就要用“$all”。

例如,我們有這樣一個collection

>  db.food.insert({ " _id "  :  1 " fruit "  : [ " apple " " banana " " peach " ]})
>  db.food.insert({ " _id "  :  2 " fruit "  : [ " apple " " kumquat " " orange " ]})
>  db.food.insert({ " _id "  :  3 " fruit "  : [ " cherry " " banana " " apple " ]})

要查詢既有apple又有banana的document

>  db.food.find({fruit : {$all : [ " apple " " banana " ]}})
{
" _id "  :  1 " fruit "  : [ " apple " " banana " " peach " ]}
{
" _id "  :  3 " fruit "  : [ " cherry " " banana " " apple " ]}

$all的值里邊的元素是沒有順序的,只要目標包含里邊的每個元素即可,如果不使用$all,就會執行精確的匹配檢查。

>  db.food.find({ " fruit "  : [ " apple " " banana " " peach " ]})

上邊這個查詢會匹配第一個document

>  db.food.find({ " fruit "  : [ " apple " " banana " ]})

這個查詢就不會匹配第一個document

>  db.food.find({ " fruit "  : [ " banana " " apple " " peach " ]})

同樣,這個查詢也不會匹配第一個document

如果要匹配數組里指定的元素,就要使用key.index這種語法

>  db.food.find({ " fruit.2 "  :  " peach " })

index 是從0開始索引的

$size

按照數組中元素的個數查詢

>  db.food.find({ " fruit "  : { " $size "  :  3 }})

$size 不能和比較條件符號(如$gt)聯合使用

$slice操作符

上邊曾經提到過,使用find方法的第二個參數,指定哪些鍵值會被返回,$slice操作符可以返回數組元素的一個子集。

例如,查找博客和它的前10條評論

>  db.blog.posts.findOne(criteria, { " comments "  : { " $slice "  :  10 }})

或者,如果想要后10條評論的話

>  db.blog.posts.findOne(criteria, { " comments "  : { " $slice "  :  - 10 }})

也可以返回中間的一段結果

>  db.blog.posts.findOne(criteria, { " comments "  : { " $slice "  : [ 23 10 ]}})

上邊的查詢跳過前23個元素,返回第24個到第34個元素。

查詢嵌入的document

查詢嵌入的整個document和普通的查詢沒有差別,比如我們有這樣一個document

{
        " name "  : {
                " first "  :  " Joe " ,
                " last "  :  " Schmoe "
        },
        " age "  :  45
}

那么我們就可以這樣查詢名字叫Joe Schmoe的人

>  db.people.find({ " name "  : { " first "  :  " Joe " " last "  :  " Schmoe " }})

如果Joe要加一個middle name,這個查詢就不行了,這種查詢必須匹配整個嵌入的document,而且key是有順序的。

我們可以用.來直接查詢嵌入的key。

>  db.people.find({ " name.first "  :  " Joe " " name.last "  :  " Schmoe " })

點號在查詢用document里就被解釋為“通向嵌入的document”,所以document的key里邊不能包含點號。

$where 查詢

$where子句允許你在查詢里執行arbitary javascript,使你幾乎可以在查詢中做任何事情。

最常見的例子就是比較document里邊兩個key的值。舉個例子,我們有個list,我們想返回里邊的key的值里有相等的document

(隨便哪兩個key,只要它們的value相等即可)。

>  db.foo.insert({ " apple "  :  1 " banana "  :  6 " peach "  :  3 })
>  db.foo.insert({ " apple "  :  8 " spinach "  :  4 " watermelon "  :  4 })

第二個document里,菠菜和西瓜的值是相等的,這個應該返回,這個使用$條件查詢符號是做不到的。

>  db.foo.find({ " $where "  :  function  () {
... 
for  ( var  current  in   this ) {
... 
        for  ( var  other  in   this ) {
... 
                if  (current  !=  other  &&   this [current]  ==   this [other]) {
... 
                        return   true ;
...                 }
...         }
... }
... 
return   false ;
... }});

如果函數返回true,那么這個document就會作為結果集的一部分被返回。

剛才我們定義了一個函數,給$where查詢指定一個字符串是一樣的效果

>  db.foo.find({ " $where "  :  " this.x + this.y == 10 " })
>  db.foo.find({ " $where "  :  " function() { return this.x + this.y == 10; } " })

這兩個查詢是等價的。

如非必要,盡量不要使用$where查詢,$where查詢比一般的查詢慢很多,每個document都必須從BSON轉換為一個javascript對象,

然后執行$where表達式。而且,索引也不能使用。你可以通過組合使用$where查詢和非$where查詢來降低使用它的代價。

4.游標

find方法使用游標返回查詢結果,游標的客戶端實現使你可以對最終結果做很多的控制。在shell里創建一個游標很簡單,往collection里放些document,

執行查詢,將返回結果指派給一個本地變量即可。

>   for (i = 0 ; i < 100 ; i ++ ) {
... db.c.insert({x : i});
... }
>   var  cursor  =  db.collection.find();

你可以使用next方法來遍歷結果,使用hasNext方法來檢查有沒有下一個,典型的循環如下

>   while  (cursor.hasNext()) {
... obj 
=  cursor.next();
... 
//  do stuff
... }

cursor類同樣實現了iterator接口,所以你可以使用forEach循環

>   var  cursor  =  db.people.find();
>  cursor.forEach( function (x) {
... print(x.name);
... });
adam
matt
zak

當你調用find方法的時候,shell并不會立刻去查詢數據庫,直到你真正請求結果的時候才發送查詢,這樣你可以在實際執行查詢之前

追加一些其他的選項,游標的這些方法幾乎都是返回游標本身,所以你可以按任意順序鏈入這些方法,下邊三個查詢是等價的

>   var  cursor  =  db.foo.find().sort({ " x "  :  1 }).limit( 1 ).skip( 10 );
>   var  cursor  =  db.foo.find().limit( 1 ).sort({ " x "  :  1 }).skip( 10 );
>   var  cursor  =  db.foo.find().skip( 10 ).limit( 1 ).sort({ " x "  :  1 });

在這個時候查詢并未執行,所有的函數都只是構建查詢。現在我們調用hasNext方法,

>  cursor.hasNext()

這時候查詢被送到服務器,shell就立刻取到了前100條結果或者前4MB的結果,所以接下來在調用next方法的時候就不會再發送請求

以及接受結果,當第一次返回的結果集用完的時候,shell會再次聯系服務器,請求更多的結果。

Limits, Skips, 和Sorts

limit函數限制返回的結果集的上限,如,只返回3個結果

>  db.c.find().limit( 3 )

skip函數跳過前x個結果,返回剩余的

>  db.c.find().skip( 3 )

sort方法使用一組鍵值對做參數,key是document里的key的名字,value是1升序或者-1降序。

如,按名字升序和年齡降序排序

>  db.c.find().sort({username :  1 , age :  - 1 })

比較順序

mongoDB有一個關于各種數據類型之間比較的等級制度。在某些情況下,你可能有一個key,它的值有多種類型,如果你想按照該

key排序,mongoDB有一個預定義好的順序,它們從小到大分別為:

        1. Minimum value
        2. null
        3. Numbers (integers, longs, doubles)
        4. Strings
        5. Object/document
        6. Array
        7. Binary data
        8. Object ID
        9. Boolean
        10. Date
        11. Timestamp
        12. Regular expression
        13. Maximum value

獲取一致性的結果

處理數據的常見方式就是從mongoDB里取出來,然后修改它,再存儲進mongoDB,如下:

 

cursor  =  db.foo.find();

while  (cursor.hasNext()) {
        var  doc  =  cursor.next();
        doc 
=  process(doc);
        db.foo.save(doc);
}

結果集數量很小的時候,這樣子做是沒問題的,如果結果集很大,這個做法就行不通了。

想象一下document是如何存儲的,你可以像下圖4-1一樣來理解存儲document的collection,每個雪花代表一個document,因為

它們和document一樣,都很漂亮而且獨一無二。

MongoDB權威指南(3)- Querying

圖4-1

現在,我們執行一個find,它從頭開始向右返回結果。程序獲取前100個document并處理它們,然后將它們存儲回數據庫,如果一個

document沒有足夠的空間來容納新的document,如圖4-2,那么就需要給它重新指定存儲位置。通常,這個新的document 會重新

定位到collection的尾部,如圖4-3.

MongoDB權威指南(3)- Querying

圖4-2

MongoDB權威指南(3)- Querying

圖4-3

現在,我們的程序持續不斷地批量獲取document,當它到達collect尾部的時候,那些被重新定位的document會再次返回,如圖4-4.

MongoDB權威指南(3)- Querying

圖4-4

這個問題的解決辦法是,對查詢使用snapshot,如果添加了“$snapshot"選項,查詢就會按照collection未被改變的視圖運行。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!