一、簡述 MongoDB中使用find來進行查詢。查詢就是返回一個集合中文檔的子集,子集合的範圍從0個文檔到整個集合。預設情況下,"_id"這個鍵總是被返回,即便是沒有指定要返回這個鍵。("_id"是一個集合中每個文檔的唯一標識) 查詢的使用上有限制,傳遞給資料庫的查詢文檔必須是常量。(當然,在你的 ...
一、簡述
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預設在快照上使用查詢。