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(匹配數組)
  --查詢匹配有兩種。數組匹配和非數組匹配。非數組匹配必須鍵的值滿足每一條查詢條件才行。數組匹配只要鍵的數組元素分別滿足查詢條件即可。比如:

            

     

                -- $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默認在快照上使用查詢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM