天天看點

MongoDB系列一(查詢).

一、簡述

    MongoDB中使用find來進行查詢。查詢就是傳回一個集合中文檔的子集,子集合的範圍從0個文檔到整個集合。預設情況下,"_id"這個鍵總是被傳回,即便是沒有指定要傳回這個鍵。("_id"是一個集合中每個文檔的唯一辨別)

    查詢的使用上有限制,傳遞給資料庫的查詢文檔必須是常量。(當然,在你的代碼裡可以是正常的變量)

    一個鍵可以有任意多個條件,但是一個鍵不能對應多個更新修改器。

    條件語句是内層文檔的鍵,而修改器是外層文檔的鍵。

二、使用find或者findOne函數和查詢文檔對資料庫執行查詢

1、db.userInfo.find()

--查詢所有資料,相當于 select * from userInfo

2、db.userInfo.find({age:22})

--查詢 age = 22 的記錄,相當于 select * from userInfo where age = 22

3、db.userInfo.find({age:22,name:'zhangsan'})

--查詢 age = 22 并且name = 'zhangsan' 的記錄,相當于  select * from userInfo where age = 22 and name = 'zhangsan'

tips:比對正規表達式(4、5):

4、db.userInfo.find({name:/mongo/})

--查詢 name 中包含 mongo 的資料, 相當于 select * from userInfo where name like '%mongo%'

5、db.userInfo.find({name:/^mongo/})

--查詢 name 中以mongo開頭的,相當于 select * from userInfo where name like 'mongo%'

6、db.userInfo.findOne()

--查詢第一條資料,相當于 select top 1 * from userInfo 與 db.userInfo.find().limit(1)

7、db.userInfo.distinct("name")

--查詢後去掉目前集合中的某列的重複資料,相當于 select distinct name from userInfo

tips:find 查詢的第一個大括号表示查詢條件,第二個大括号表示要顯示的字段(預設全顯示 ):

8、db.userInfo.find({},{name:1,age:1})

--查詢指定列name、age的資料,相當于 select name,age from userInfo

9、db.userInfo.find({},{name:0})

--不希望結果集中顯示 name 這個字段

tips:排序分頁

10、db.userInfo.find().sort({age:1})

--按照年齡升序

11、db.userInfo.find().sort({age:-1})

--按照年齡降序

12、db.userInfo.find().limit(5)

--查詢前5條資料,相當于 select top 5 * from userInfo

13、db.userInfo.find().skip(10)

--查詢 10條以後的資料 select * from userInfo where id not in (select top 10 * from userInfo)

14、db.userInfo.find().limit(5).skip(0)

--可用于分頁 limit是pageSize,skip是 第幾頁*pageSize(從第0頁開始)

15、db.userInfo.find({sex:null}) 

-- 特定類型的查詢,比如 null 。它确實可以比對自身,但是它不僅可以比對這個鍵為 null 的文檔,也能比對不包含這個鍵的文檔。如果僅想比對這個鍵位 null 的文檔,需要修改如下:

-- db.userInfo.find({sex:{'$in':[null],'$exists':true}})

三、使用$條件查詢實作範圍查詢、資料集包含查詢、不等式查詢,以及其他一些查詢

1、$lt(小于)、$lte(小于等于)、$ge(大于)、$gte(大于等于)、$ne 不等于

db.userInfo.find({age:{$gt:22}})

    --查詢age > 22 的記錄,相當于 select * from userInfo where age > 22

db.userInfo.find({age:{$lt:22}})

    --查詢age < 22 的記錄,相當于 select * from userInfo where age < 22

db.userInfo.find({age:{$gte:22}})

   --查詢 age >= 22 的記錄,相當于 select * from userInfo where age >= 22

db.userInfo.find({age:{$lte:22}})

   --查詢 age <= 22 的記錄 ,相當于 select * from userInfo where age <= 22

db.userInfo.find({age:{$gte:23,$lte:26}})

   --查詢 age >= 23 并且 age <=26 的記錄 , 相當于 select * from userInfo where age >= 23 and age <=26

db.userInfo.find({age:{$ne:23}})

    --查詢 age != 23 的記錄 , 相當于 select * from userInfo where age != 23

tips:很遺憾,并沒有 $eq(等于)這個操作符。

2、元條件句 $and 、$or、$not

         元條件句:即可以用在任何其他條件之上 。

$and 總是希望盡可能用最少的條件來限定結果的範圍

db.userInfo.find({"$and" : [{x : {"$lt" : 1}}, {x : 4}]})

    --會比對那些"x"字段的值小于1并且等于4的文檔。雖然這兩個條件看起來是沖突的,但是這是完全有可能的,比如,如果"x"字段的值是這樣一個數組{"x" : [0,4]},那麼這個文檔就與查詢條件相比對。

    --查詢優化器不會對"$and"進行優化,這與其他操作符不同。如果把上面的查詢改成下面這樣,效率會更高:db.userInfo.find({x : {"$lt" : 1, "$in" : [4]}})

$or 第一個條件應該盡可能比對更多的文檔,這樣才是最為高效的

db.userInfo.find({$or:[{age:22},{age:25}])

    --or與查詢,相當于select * from userInfo where age = 22 or age = 25

$not 用在其他條件上的取反,雖然是元條件句,但是不能放在外層文檔(否則:unknown top level operator: $not),并且後面必須跟正規表達式或者文檔(否則:$not needs a regex or a document)。

db.userInfo.find({age:{'$not':{$gt:23}}});

    -- 對于 age>23 傳回的文檔取反集

db.product.find({name:{$not:/ang/}});

   -- 對 name 與正則比對的結果取反集合

3、$in、$nin、$all、$size、$slice 、$elemMatch

$in 可以用來查詢一個鍵的多個值

 db.userInfo.find({age : {"$in" : [22, 23, 24]}})

    --查詢年齡等于22、23、24的文檔

$nin 與 $in 相反,用來查詢一個鍵不屬于多個值的文檔。

$all (比對數組)

db.food.find({fruit : {$all : ["apple", "banana"]}})

   -- 查詢 fruit 既含有 apple,又含有banana 的文檔。

   -- 當然,也可以不使用$all 比對數組,比如 db.food.find({fruit : ["apple", "banana","orange"]}) 但是,這樣子隻能唯一比對數組為["apple", "banana","orange"] 的文檔,而且查詢數組條件還要保證相同的元素順序。

   --可以使用 key.index 查詢數組特定位置的元素。db.food.find({"fruit.2" : "peach"})

$size(比對數組)

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

    --比對數組長度為3的文檔 

$slice(比對數組)

  --$slice 用在find的第二個參數,用來查找某個鍵比對的數組元素的一個子集。

  --使用"$slice"時将傳回文檔中的所有鍵。

  --db.blog.findOne({},{comments:{"$slice":2}}) 傳回 結果文檔中comments數組的前兩個子集

  --db.blog.findOne({},{comments:{"$slice":[23,10]}}) 傳回 結果文檔中comments數組的 24-33 子集,不夠則全傳回。

  --db.blog.findOne({},{comments:{"$slice":-1}}) 傳回 結果文檔中comments數組的最後一個子集 

$elemMatch(比對數組)

  --查詢比對有兩種。數組比對和非數組比對。非數組比對必須鍵的值滿足每一條查詢條件才行。數組比對隻要鍵的數組元素分别滿足查詢條件即可。比如:

MongoDB系列一(查詢).
MongoDB系列一(查詢).

                         -- $elemMatch 可以讓數組的元素分别要滿足查詢條件,但是 $elemMatch 不會比對非數組元素!!

                         -- db.test.find({"x" : {"$elemMatch" : {"$gt" : 10, "$lt" : 20}}) 

4、其他 $exists 、$mod

$exists

   --查詢某個鍵時候存在

  -- db.userInfo.find({sex:{$exists:true}}) 傳回鍵名含有sex的文檔

  -- db.userInfo.find({sex:{$exists:false}}) 傳回鍵名不含有sex的文檔

$mod

  --$mod會将查詢的值除以第一個給定值,若餘數等于第二個給定值則比對成功

  -- db.userInfo.find({id : {"$mod" : [5, 1]}}

四、查詢将會傳回一個資料庫遊标,遊标隻會在你需要時才将需要的文檔批量傳回 

資料庫使用遊标傳回find的執行結果。用戶端對遊标的實作通常能夠對最終結果進行有效的控制。可以限制結果的數量,略過部分結果,根據任意鍵按任意順序的組合對結果進行各種排序,或者是執行其他一些強大的操作。

var cursor = db.driverLocation.find();
while (cursor.hasNext()){
     var object = cursor.next();
     print(object.type);
    }      

遊标類還實作了JavaScript的疊代器接口,是以可以在forEach循環中使用:

var cursor = db.driverLocation.find();
cursor.forEach(function(x){
    print(x.type);
    });          

    調用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});

此時,查詢還沒有真正執行,所有這些函數都隻是構造查詢。當執行 cursor.hasNext() 的時候,查詢才真正被執行。這時,查詢被發往伺服器。shell立刻擷取前100個結果或者前4 MB資料(兩者之中較小者),這樣下次調用next或者hasNext時就不必再次連接配接伺服器取結果了。用戶端用光了第一組結果,shell會再一次聯系資料庫,使用getMore請求提取更多的結果。getMore請求包含一個查詢辨別符,向資料庫詢問是否還有更多的結果,如果有,則傳回下一批結果。這個過程會一直持續到遊标耗盡或者結果全部傳回。

遊标的生命周期:首先,遊标完成比對結果的疊代時,它會清除自身。另外,如果用戶端的遊标已經不在作用域内了,驅動程式會向伺服器發送一條特别的消息,讓其銷毀遊标。最後,即便使用者沒有疊代完所有結果,并且遊标也還在作用域中,如果一個遊标在10分鐘内沒有使用的話,資料庫遊标也會自動銷毀。

五、還有很多針對遊标執行的元操作,包括忽略一定數量的結果,或者限定傳回結果的數量,以及對結果排序。 

   -- MongoDB處理不同類型的資料是有一定順序的。有時一個鍵的值可能是多種類型的,例如,整型和布爾型,或者字元串和null。如果對這種混合類型的鍵排序,其排序順序是預先定義好的。優先級從小到大,其順序如下:

1. 最小值;

2. null;

3. 數字(整型、

4. 字元串;

5. 對象/文檔;

6. 數組;

7. 二進制資料

8. 對象ID;

9. 布爾型;

10. 日期型;

11. 時間戳;

12. 正規表達式

13. 最大值 。

    -- 不用 skip 進行分頁

    如前文提到的,一般分頁我們用 db.userInfo.find().limit(pageSize).skip(第n頁 * pageSize) 來實作。但是我們注意到,如果資料量大的話,我們總是先取出前 n*pageSize 的條數然後再舍棄掉,顯得很不合算。為此,《MongoDB權威指南》向我們介紹了一種方式:利用時間進行排序,拿到前一頁的最後時間,取出時間大于上一頁最後時間的 pageSize 條記錄,如下:

var latest = null;
//顯示第一頁
var page1 = db.foo.find().sort({"date" : -1}).limit(100)
while (page1.hasNext()) {
latest = page1.next();
display(latest);
}
// 擷取下一頁
var page2 = db.foo.find({"date" : {"$gt" : latest.date}});
page2.sort({"date" : -1}).limit(100);      

 但是,我發現這樣寫還是會存在很多問題,比如說:

1、跟上一頁最後一個文檔的時間一樣的文檔如果有多個呢?那這樣不是會導緻一些文檔被漏掉了嗎?

2、上一頁、下一頁或許可以解決。那麼如果使用者點選第四頁、第五頁呢?

    -- 擷取一緻結果

    資料處理通常的做法是先将資料從資料庫中取出來,做一些變換以後,再儲存回資料庫。但是,MongoDB這邊有個機制就是,如果拿出來處理的資料處理後導緻體積比原先大很多,會導緻資料放不回原來的位置,而把這個資料挪至集合的末尾處。進而引發的隐患就是:分頁查詢到最後一頁的時候,又取到了原來的資料。

    應對這個問題的方法就是對查詢進行快照(snapshot)。如果使用了這個選項,查詢就在"_id"索引上周遊執行,這樣可以保證每個文檔隻被傳回一次。

    db.foo.find().snapshot()

    快照會使查詢變慢,是以應該隻在必要時使用快照。例如,mongodump預設在快照上使用查詢。