有段時間沒看書了,記錄下這個周末看《MongoDB權威指南》的筆記,目前看到是第四章:查詢
- find({查詢條件限定},{返回字段})
這是一個查詢的基本語法,各個簽名的作用已經說得很清楚。下面來細細展開:
當然最開始插入一批數據以供測試:
- db.users.insert({"_id": 1, "name": "aroba", "age": 22, "friends": 3})
- db.users.insert({"_id": 2, "name": "brob", "age": 23, "friends": 4})
- db.users.insert({"_id": 3, "name": "robin", "age": 24, "friends": 23})
- db.users.insert({"_id": 4, "name": "ccrob", "age": 25, "friends": 32})
- db.users.insert({"_id": 5, "name": "drobin", "age": 26, "friends": 15})
- db.users.insert({"_id": 6, "name": "rrobin", "age": "az", "friends": 19})
1、 方法中的第一個參數:查詢條件限定是一個document結構,如為{}將默認返回所有數據
- #查詢一個年齡為27的用戶:
- db.users.find({"age":23})
- #查詢一個年齡為27,姓名為robin的用戶,相當於AND
- db.users.find({"age":23, "name":"brob"})
作為文檔的查詢條件,可以支持更復雜的格式:
- #查詢年齡大於20且小於30的用戶
- db.users.find({"age":{"$gt":20, "$lte":23}})
這里條件查詢有常用的:小於("$lt")、小於等於("$lte")、大於("$gt")、大於等於("$gte")、不等於("$ne") 。這些條件查詢對數字日期類型的字段比較適用
前面說到同時查詢age和name屬性,相當於AND查詢。這里來看看OR查詢,主要通過"$in"和"$or" 。對單一鍵有多個值與其匹配的話就用"$in",后面跟一個條件數組。
- #查詢年齡在某個范圍的用戶:
- db.users.find({"age":{"$in":[20, 22, 25]}})
"$in" 對支持的類型非常靈活,不同類型的條件可以同時查詢。與之相對應的就是"$nin",表示不在該范圍內的鍵
- db.users.find({"age":{"$in":[20, 22, 25, "az"]}})
- db.users.find({"age":{"$nin":[20, 22, 25, "az"]}})
與單一鍵的"$in"不同的是,"$or"是包含多個可能條件的數組。
- #年齡在某個范圍內或者name在某個范圍內的用戶
- db.users.find({"$or": [{"age": 23 }, {"name": "robin"}]})
- db.users.find({"$or": [{"age": {"$in": [ 23, 4, "az" ]} }, {"name": "robin"}]})
2、返回字段
作為查詢的第二個參數,如果沒有的話是默認返回所有字段。可以對需要的返回字段指定:
- db.users.find({}, {"name":1, "age":1})
1、這個查詢會返回name、age、_id字段
2、_id是默認返回,如果不要顯示加上("_id":0)
- db.users.find({}, {"name":1, "age":1, "_id":0})
3、如果某個字段如age不存在,也不拋異常
4、需要顯示的字段設置為大於零的數就可以,但還是用1好理解,但如果對不需要顯示的字段且不是_id設置為0或其他會拋異常
- db.users.find({}, {"name":1, "age":0, "_id":0})
這樣是不行的,如果不要返回age不加上就可以了
后面還將對數組查詢的返回字段做相應的說明,這里就先到此
3、幾點說明
1、"$not"元條件句,用在其他任何條件上,如
- #age>20即查詢age小於等於20的用戶
- db.user.find({"age": {"$not" : {"$gt" :20}}})
- #這里查詢age不是1,6,11,16...等的用戶
- db.user.find({"age": {"$not": {"$mod": [5, 1]}}})
2、條件查詢與更新修改器
- #更新修改器
- db.users.update({"age": 23},{"$set":{"name":"zzzz"}})
- #條件查詢
- db.users.find({"age": {"$gt": 20}})
條件句是內層文檔鍵,修改器是外層文檔鍵。而且對同一個字段age來說可以是多個限定條件,但是修改器不能對應多個
3、null
如果某個字段的值為null,根據null來查詢時可以返回該條文檔,但也會返回不包含該字段的文檔
- #新增兩條數據
- >db.users.insert{ "_id" : 7, "age" : 23, "name" : "joe" }
- >db.users.insert{ "_id" : 8, "age" : 24, "friends" : null, "name" : "sam" }
查詢鍵值為null的字段
- >db.users.find({"friends": null})。
這里會返回friends為null的文檔,但是也會返回沒有該鍵的文檔
- { "_id" : 7, "age" : 23, "name" : "joe" }
- { "_id" : 8, "age" : 24, "friends" : null, "name" : "sam" }
需要通過"$exists"來判定鍵值是否存在
- > db.users.find({"friends":{"$in":[null],"$exists":true}})
- { "_id" : 8, "age" : 24, "friends" : null, "name" : "rrbin" }
4、正則表達式
- #這里會返回所有name中包含rob字段的文檔
- > db.users.find({"name":/rob/})
- #不僅對字段值進行正則匹配,如果值本身是正則式也匹配
4、數組查詢
插入幾條數據測試
- db.food.insert({"_id": 1, "fruit": ["apple", "banana", "peach"]})
- db.food.insert({"_id": 2, "fruit": ["apple", "orange"]})
- db.food.insert({"_id": 3, "fruit": ["banana", "peach", "orange"]})
以下是一些常用的查詢方法,直接上
- #匹配fruit中包含banana的文檔
- db.food.find({"fruit": "banana"})
- #必須匹配所有
- db.food.find({"fruit": {"$all" : ["apple", "peach"]}})
- #精確匹配
- db.food.find({"fruit": ["apple", "orange"]})
- #指定下標 key.index
- db.food.find({"fruit.2": "peach"})
- #查詢指定長度的數組
- db.food.find({"fruit": {"$size" : 3}})
但是"$size"操作只能嚴格匹配,遇到比如要求數組大於或者小於之類的查詢就無能為力了。這里提供了解決的方案:對文檔新增size字段,每次對數組push或pop操作時,對size字段做相應的增減。查詢的時候再對字段size做相應的處理
- db.food.update({"$push" :{"fruit" : "strawberry"} , "$inc" : {"size" : 1}})
- db.food.find({"size" : {"$gt" : 3}})
返回數組指定子集
"$slice"用於返回數組的一個子集,支持前、后或者偏移
- db.food.insert({"_id": 4, "fruit": ["apple", "banana", "peach", "orange", "watermelon", "lemon", "cherry"]})
- #取前2個
- db.food.find({"_id":4}, {"fruit":{"$slice":2}})
- #{u'_id': 4, u'fruit': [u'apple', u'banana']}
- #取后兩個
- db.food.find({"_id":4}, {"fruit":{"$slice":-2}})
- #{u'_id': 4, u'fruit': [u'lemon', u'cherry']}
- #從第2個開始取三個,這個其實達到分頁的效果,但書中明確指出對大量數據skip性能下降厲害,不建議考慮這種方式
- db.food.find({"_id":4}, {"fruit":{"$slice":[2, 3]}})
- #{u'_id': 4, u'fruit': [u'peach', u'orange', u'watermelon']}
使用"$slice"獲取數組內的值時,其他的鍵也會默認返回,如果不需要返回非數組內的其他鍵這里可以指明,與前面返回不同的是這里可以用0
- db.food.insert({"_id": 5, "sum":7, "fruit": ["apple", "banana", "peach", "orange", "watermelon", "lemon"]})
- db.food.find({"_id":5}, {"fruit":{"$slice":[2, 3]}, "_id":0, "sum":0})
- {u'fruit': [u'peach', u'orange', u'watermelon']}
5、查詢內嵌文檔
這里主要考慮匹配查詢內嵌文檔,考慮如下文檔
- db.users.insert({"_id": 9, "age":23, "name": {"first":"joe", "last":"sam"}})
- db.users.insert({"_id": 10, "age":24, "name": {"first":"joe", "middle":"dd", "last":"sam"}})
查詢名字為joe sam的用戶
- #查詢名字為joe sam的用戶
- data = db.users.find({"name":{"first":"joe", "last":"sam"}})
- #返回第一條,實際上這相當於精確匹配,這個查詢條件將嚴格匹配順序、字段的數量。其實第二條也是我們想要的結果,那么正確的寫法應該是:
- data = db.users.find({"name.first":"joe", "name.last":"sam"})
書中說到一種復雜情況下的查詢:joe發表的5分以上的評論:
- db.blog.insert({"_id":1, "content":"....", "comments":[{"name":"joe", "score":3, "comment":"nice"}, {"name":"sam", "score":5, "comment":"zzz"}, {"name":"joe", "score":5, "comment":"good"}]})
- data = db.blog.find({"comments":{"name":"joe", "score":{"$gte":5}}})
- #這樣是查不到數據的,內嵌文檔要求匹配整個文檔,而不是comments鍵
- data = db.blog.find({"comments.name":"joe", "comments.score":{"$gte":5}})
- #這個查詢會返回這條記錄,其實是匹配的commets中各個鍵,即joe匹配第一條,score匹配第二條
- data = db.blog.find({"comments":{"$elemMatch":{"name":"joe", "score":{"$gte":5}}}})
6、分頁
分頁在前面說到"$slice"時,其實是達到分頁的效果,前面也說了弊端,這里進一步說明。這里采用limit限制返回結果,slice跳過指定數量文檔,sort對查詢結果排序
limit
db.users.find().limit(3)結果集超過三條返回三條,不足返回實際數量,貌似對負數不感冒,比如-2還是返回前兩條,或者limit里沒有偏移這個概念
skip
db.users.find().limit(3)省略結果集前三個,返回剩下的,結果集不足三個就啥都木有了,當然這個也一樣,別寫負數了,否則拋異常
sort
對結果集排序:1升序,-1降序。可支持多個鍵/對
- db.users.find().sort([("name", 1), ("age", -1)])
- db.users.find().sort({"name":1,"age":-1})
- #這就是分頁
- db.users.find().limit(2).sort("_id", 1)
- db.users.find().limit(2).skip(2).sort("_id", 1)
- db.users.find().limit(2).skip(4).sort("_id", 1)
這對大數據量的skip性能影響較大,這里也提供了一些繞過的方法。比如先取得最后一條的記錄的某個唯一鍵,再查詢大於該鍵的值。可以看出這個限制條件挺多,當然容易想到的采用主鍵"_id",這是主鍵必須是數字了
書中還說到了其他的一些高級特性,如$where、隨機獲取、包裝查詢、獲取一致性結果等,這里就沒記錄下來