一、簡述
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預設在快照上使用查詢。