MongoDb查詢詳解

來源:互聯網
上載者:User

標籤:blog   io   os   使用   java   ar   資料   問題   sp   

查詢合格第一個文檔(對於mongo來說不能叫記錄了)
db.COLLECTION_NAME.findOne({},{});
    
查詢合格文檔,並按照指定條件排序,跳過前面N1個文檔,返回最多數量為N2的文檔列表
sort skip limit三個函數可選
db.COLLECTION_NAME.find({},{}).sort({}).skip(N1).limit(N2);

返回條件的文檔數量
db.COLLECTION_NAME.count({});

上面三行代碼就是mongo的世界裡查詢語句的全部。
findOne find count sort的參數都包含在花括弧裡。用mongo的術語來說就是,它們的參數也是一個文檔,而且值得一提的是,在mongo的世界裡函數參數如果不是文檔就是基本類型。在與查詢有關的參數裡就是skip和limit以整數為參數,一些與重新命名集合、建立集合、分區的初始化與管理等函數以字串為參數。其它絕大部分函數參數是是以文檔形式傳遞。
    
find和findOne的第一個參數是查詢條件,第二個參數是指定返回條例查詢條件的那些文檔裡的哪些欄位,第二個參數可省略。
mongo的文檔也就是json對象。不過這些作為函數參數的文檔,它們的屬性名稱是有特殊含義的。

find和findOne的第一個參數:
第一個參數的屬性名稱是要查詢的文檔內的屬性名稱,屬性的值就是針對這個屬性名稱的查詢條件。
假定我們有以下資料庫:
use TEST;
//db.createCollection(‘USER‘);
db.USER.findOne({name:‘Tom‘,password:‘passkey‘});
這行代碼將從集合USER裡面查詢屬性名稱有name和password,且值分別是‘Tom‘和‘passkey‘的第一個文檔

查詢參數的屬性值如果是簡單類型的值,mongo就會像上文一樣將查詢條件按相等性處理。更複雜的查詢將在後續文章介紹。
上文的findOne調用將會把合格文檔全部屬性都返回,如果我們只需要文檔中的一部分屬性這個時候查詢函數的第二個參數就派上用場了。
db.USER.findOne({name:‘Tom‘,password:‘passkey‘},{name:1,registTime:1});
這行代碼仍然會命中資料庫內相同的文檔,只是並不會返回被命中文檔的全部屬性,而是只返回name和registTime這兩個屬性。
之前提到過mongo不是模式嚴格的資料庫,也就是說同一個集合內的文檔不一定有相同的屬性,有些文檔可能包含更多的屬性,同名的屬性在不同文檔裡可能是不同的資料類型。

假如說USER集合裡面有一部分老資料沒有registTime,後來由於需求變更新使用者都增加了registTime。
如果是在關聯式資料庫裡,肯定所有記錄都增加了一個registTime欄位,而且必須要首先修改資料表定義才行。不過,現在是mongo的世界了。我們可以在需求發生變化的時候,隨時修改程式的代碼而不必顯式修改資料庫模式的定義。這樣勢必就會造成同一個資料集合裡有一部分舊資料沒有新增的欄位。這種情況下,那些沒有registTime而又符合查詢條件的文檔會被怎麼處理呢?答案就是這些文檔依然會被返回,如果文檔中有registTime就返回registTime,如果沒有就不返回。換句話說,如果把資料庫中的文件屬性名看作數學意義上的集合,find*函數的第二個參數的屬性名稱是另一個集合;那麼返回的結果集中的文件屬性名就是前面兩個集合的交集。

find函數的兩個參數與findOne完全一樣。就不多說了。

值得一提的是,除非在find*函數的第二個參數顯式指定_ID:0,否則_ID屬性預設會包含在結果集中。
 
sort的參數
顧名思義,就是針對被命中文檔排序。
它的參數是一個文檔。文件屬性名是要排序的屬性,屬性的值只能取1或-1,1表示升序,-1表示降序。

skip(N1)
它會跳過結果集的前N1個文檔,但是它仍然會掃描這N1個文檔,因此skip的效率很低。
為保證效率應在調用skip之前使結果盡量的小。
最好的做法是在find函數的第一個參數文檔裡指定屬性,這些屬性與sort函數的參數文檔裡的屬性一樣,如果是升序排序就指定返回這些屬性的值大於上一頁的最大值的文檔,反之就指定返回它們小於上一頁的最小值的文檔。用這種方式代替skip。

limit(N2)
這是查詢語句裡面最後調用的函數,意思就是最多返回命中結果集裡面的N2個文檔。

關於複雜的查詢條件,比如大於、小於、存在性、空值等比較將在下一篇文章裡介紹。
mongo的查詢條件甚至還支援Regex查詢,可以在查詢語句裡嵌入javascript代碼,支援嵌套子文檔和數組索引的查詢條件等。它也可針對嵌套子文檔和數組建立索引。


上面提到的查詢都是等值條件查詢,但是我們更多的時候需要模糊查詢、非等值查詢、模式比對等。mongo不是key-value儲存,它支援非常靈活複雜的查詢方式,甚至比rdbms還要靈活的多,當然也複雜的多。
    
另外,需要多說一點,用nosql歸類這些資料庫並不準確,只是RDBMS都是用SQL的,而它們都是不用SQL的,所以就用nosql來歸類這些資料庫了。其實從技術上考慮,完全可以實現一個非RDBMS而繼續使用SQL的全部特性來操作和管理資料庫。當然為了表達方便這一系列文章仍然使用nosql這一併不準確的名詞。

既然沒有了sql,要操作mongo自然就要使用其它的方式了。前面的文章都已經出現過一些了,就是用mongo定義的資料庫操作api配合它的文檔形式的巨集指令引數完成資料庫的建立、修改、刪除和資料的增刪改查。

關於查詢的參數在上一篇幾乎已經說完,還剩下的就是find*和count的第一個參數。
由於find*的第一個參數和count參數都一樣,本文就只以find函數做說明。
    
查詢某個欄位比指定值小:$lt
//假設存在集合USERdb.USER.find({REGIST_DATE:{$lt:new Date(2013,0,1)}});
/*前面提到過mongo完全遵守JAVASCRIPT文法,在JAVASCRIPT裡面,月份是從0開始的,即上面的查詢是查詢2013-1-1以前註冊的的使用者。*/
查詢某個欄位比指定值大:$gt
db.USER.find({REGIST_DATE:{$gt:new Date(2013,0,1)}});
/*$lt表示小於,表示大於的自然就是$gt了*/
大於等於:$gte   小於等於:$lte
db.USER.find({REGIST_DATE:{$gte:new Date(2013,0,1),$lte:new Date(2013,0,31)}});
/*關於這個時間的問題看起來似乎有些彆扭哈,沒辦法啦,MONGO就是這樣啦。習慣就好啦。*/
/*上面的一行查詢就是針對REGIST_DATE的組合查詢形式,表示查詢出所有在2013-1-1到2013-1-31註冊的使用者*/

接下來的查詢方式就比較複雜了
Regex,mongo裡面沒有類似sql的like特性,不過可以用Regex代替
使用Regex查詢有兩種情況,在支援Regex字面值(標量)的語言裡可以直接使用Regex字面值,比如RUBY NODEJS等。
db.USER.find({NAME:/^run/i});//以javascript為例,這個查詢出所有使用者名稱以run開頭的使用者,且不分大小寫
像JAVA這樣不支援Regex標量的語言怎麼辦呢?就有些麻煩了,需要藉助MONGO api完成從字串到Regex的轉化。
db.USER.find(NAME:{$regex:‘^run‘,$options:‘i‘});//這行命令完成跟上一行一樣的工作。
其中,$options是Regex的選項,它一共有三個字母的任意組合可選,這三個字母分別是g i m,這三個字母可以任意組合表達不同的意義。
g:表示針對整個字串做匹配,如果不加Regex在匹配到第一個符合的子串時就返回了。(global)
i:忽略大小寫(insenssitive)
m:一個字串內如果存在分行符號,將作為多行模式比對(multiple)
除了i以外其它兩個選項在查詢的時候恐怕用不到。
$in---相當於sql的in,它可以利用索引
db.USER.find(NAME:{$in:[‘tom‘,‘jerry‘]});
/*如果為NAME欄位建立了索引,它就會從索引裡面尋找*/
/*mongo是區分大小寫,所以集合和文件屬性的名字必須與建立它們的時候一致。*/
/*也就是NAME和name是兩個不同的屬性,它們可以同時存在於一個文檔內*/
/*在一個集合內如果即存在NAME屬性的文檔,又存在name屬性的文檔,那麼上面的那條命令不會把name屬性查詢出來*/
$nin---$in的相反操作,相當於sql的not in
db.USER.find(NAME:{$nin:[‘tom‘,‘jerry‘]});
注意:$nin不會利用索引,也就是說上面的命令$nin不會使用針對NAME屬性的索引。
$all---沒有sql類似的特性與之類比了,它的意義在於:查詢條件是一個簡單值數組,只有返回屬性滿足數組內的所有值的文檔。這種查詢條件只有在屬性值是一個數組的情況下。
以我的這篇博文為例。要查詢出所有含有nosql和mongo這兩個標籤的文檔可以這麼做
假設iteye要把資料庫遷移到mongo,部落格文章的資料模型就可以這樣定義
首先定義一個名為blog的集合。
這篇blog可以如下方式儲存
{
    _ID:ObjectID(............),
    subject:‘mongo簡介——查詢(續)‘,
    category:‘database‘,
    user_category:[‘nosql‘,‘mongo‘],
    content:‘.............‘,
    tags:[‘nosql‘,‘mongo‘],
    origrinal:true,
    pub:‘blog‘
}
/*origrinal表示是否原創;由於沒有附件,本文的文檔就不包含附件屬性,由於我不知道iteye如何定義頻道,我就用字串表示了*/
下面如果要查詢包含‘nosql’標籤的所有博文
db.blog.find(tags:‘nosql‘);//這樣就可以了
下面要查詢同時包含‘nosql‘和‘mongo‘這兩個標籤的博文
db.blog.find(tags:{$all:[‘nosql‘,‘mongo‘]});
如果有的博文除了包含‘nosql‘和‘mongo‘標籤,還包含‘MONGO‘ ‘Mongo‘ ‘mongodb‘ ‘MongoDB‘ ‘NOSQL’等標籤,上面的那行命令也會一起返回。
如果要進行忽略大小寫查詢,我又不想使用Regex做模糊比對該怎麼辦呢?
答案是不能。
而這樣的需求還是很常見的,那麼惟一的做法就是,在使用者儲存博文的時候,程式根據以前已經存在的標籤找出相似詞,自動建立幾個可能會出現的不同大小寫標籤。比如我儲存這篇文章的時候程式再自動建立上面提到的那幾個我沒有指定的標籤。
$ne---不等性比較,它接收單值或數組
db.blog.find(tags:{$ne:‘nosql‘});//返回所有不包含nosql標籤的博文
db.blog.find(tags:{$ne:[‘nosql‘,‘mongo‘]});//返回所有不包含nosql和mongo這兩個標籤的博文
$size---檢查一個數組的尺寸,不利用索引,不能做範圍查詢,以後可能會增加這方面的支援
有時iteye會給使用者快遞一些獎品,iteye可能會把使用者曾經填過的地址儲存下來。可以這麼儲存
在USER集合裡增加一個address屬性,沒有留過地址的可以沒有。
一個使用者不一定只有一個地址,這個address就可以建立為一個字串數組。
比如要返回所有只留了一個地址的使用者。
db.USER.find(address:{$size:1});
有些時候,如果ITEYE想要知道使用者更詳細的地址資訊,就要用更複雜的文檔儲存地址。比如:
{
     _ID:ObjectID(.........),
     accountName:‘runfriends‘,
     address:[{category:‘home‘,city:‘北京‘,district:‘東城‘,street:‘.....‘},
                    {category:‘company‘,city:‘北京‘,district:‘海澱‘,street:‘........‘}]
}
如果要查出所有家在北京的使用者,要怎麼做呢?
可能會這樣寫:
db.USER.find({‘address.category‘:‘home‘,‘address.city‘:‘北京‘})。
但是這樣是錯的!這行命令的意義是查詢出所有地址裡面分類包含home,而且地址所在城市包含北京的使用者。有些使用者的公司地址在北京,而家庭地址不是,這些使用者也會被匹配到。
那麼接下來就用到了$elemMatch
它只在需要匹配子文檔的多個屬性時才會用到
db.USER.find(address:{$elemMatch:{category:‘home‘,city:‘北京‘}});
$not---取反值,只有在沒有指定相反操作時才需要用到它。因為絕大部分操作符都有對應的相反操作,所以應當盡量使用相反操作符,比如Regex匹配沒有相反操作
假如有一天,ITEYE只允許使用者名稱以字母開頭了就可以把所有不以字母開頭的使用者查出來給他們發一封郵件,讓他們改名。
db.USER.find(accountName:{$not:/^[a-zA-Z]/})
當然$not也接收單值,但是不建議使用
$exists----檢查某個屬性的存在性。
比如要把所有包含附件的博文查詢出來。
db.blog.find({attachment:{$exists:true}});
沒有附件的就是db.blog.find({attachment:{$exists:false}});
或才可以這樣做:
db.log.find({attachment:null});//不存在
db.blog.find({attachment:{$ne:null}});//存在
前面介紹BSON的時候說過空值使用nil,但是這裡卻用了null,是因為nil是BSON的定義,這裡是JAVASCRIPT的文法
$mod-----求餘數,不利用索引
假如說某天ITEYE心血來潮要給所有年齡能被4整隊的使用者快遞一份獎品。
db.USER.find({age:{$mod:[4,0]}});//數組的第一個值是除數,第二個值是期望的餘數
$type---以屬性類型查詢
雖然不建議在同一集合的不同文檔相同屬性名稱儲存著不同類型的資料,但是有時由於程式bug或設計不嚴謹可能會出現這種情況。現在要把這種情況找出來,比如_ID屬性有的是ObjectID類型,有的是整數。下面把所有_ID是字串的文檔找出來。
db.USER.find(_id:{$type:2})
db.USER.find(_id:{$not:{$type:7}});//把所有主鍵ObjectID類型的文檔找出來
$or  $and----邏輯運算
它們的意義就不多解釋了。不過它們的用法非常有意思
比如找出所有月收入在20000以上或2000以下的使用者
db.USER.find({$or:[{salary:{$gt:20000}},{salary:{$lt:2000}}]});
找出所有月收入在8000以上,20000以下的使用者
db.USER.find({$and:[{salary:{$gte:8000}},{salary:{$lte:20000}}]});
查詢嵌套文檔和數組元素
前面的內容已經簡單介紹一些嵌套文檔和數組元素的查詢。嵌套文檔和數組的查詢遵守相同的文法規則
1. 它們都採用點號分割嵌套文檔的屬性,如果是數組的索引就用從0開始的數字表示。
db.USER.find({‘address.category‘:‘home‘});//這個是查出所有留了家庭地址的使用者
如果想知道使用者留下的第一個地址是不是家庭地址可以這麼做:
db.USER.find({‘address.0.category‘:‘home‘});
那麼如果想只返回留了家庭地址的使用者而又只返回家庭地址卻不返回其它的地址該怎麼做呢?
目前採用的資料模型恐怕做不到這一點,如果有這樣的需求,恐怕只能為不同的地址定義不同的欄位了。
當然如果代碼規範規定第一個地址必須是家庭地址,那麼可以這樣做:
db.USER.find({‘address.0.category‘:‘home‘},{‘accountName‘:1,‘address.0‘:1});
不過通常情況下,把第一個地址定義為預設地址更好一些。
$where-----接收一段javascript代碼作為查詢條件,不利用索引
假如說要查詢所有閏年出生的使用者   
db.USER.find({$where:‘var year=birthday.getFullYear();return year%4==0 && year%100>0 || year%400==0‘;});
或:
db.USER.find($where:‘function(){var year=this.birthday.getFullYear();return year%4==0 && year%100>0 || year%400==0‘}‘);

好了,關於mongo的查詢方式就這麼多了。

MongoDb查詢詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.