學習MongoDB--(4-1):MongoDB查詢(基本查詢條件操作符介紹)
從前面的學習中,我們得知MongoDB中使用find函式來進行查詢。查詢最終返回的是一個集合中文件的子集,子集合包括0個文件到這個集合中所有的文件。
【第一個查詢引數】
find函式第一個引數是一個文件,其中給出了我們要查詢集合中什麼樣文件的描述。如果我們要查詢所有文件,可以不帶任何引數呼叫find函式,或第一個引數為空文件{},如下例:
> db.people.find(); { "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" } { "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" } { "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" } > db.people.find({}); { "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" } { "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" } { "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" } >
對於第一個代表查詢引數的文件,我們該如何理解呢?比如我們執行 db.people.find({"age":18}),這個函式實際表示我們要查詢鍵"age"的值為18的文件,鍵“age”的值不等於18或不存在這個鍵的文件均不會被查詢到。再比如我們執行這個函式:db.people.find({"name":"jimmy", "age":18}),這個實際表示得是查詢鍵“name”的值為"jimmy"並且鍵"age"的值為18的文件,即查詢文件中各個條件是AND的關係!
在使用第一個引數時,我們需要注意一點:該查詢文件中“鍵值對”中的值必須為常量!
【第二個查詢引數】
上例中,我們不指定或只指定find函式第一個引數查詢得到的文件,會包含原集合文件所有的鍵值對。這種情況對於某些鍵值對特別多的文件並不適用,因為我們可能只關心該文件的某幾個鍵值對。這時我們可以使用find函式的第二個引數,來指定返回的鍵值對,這樣還可以減少傳輸的資料量從而加快效率。第二個引數同樣是個文件,如下例:
> db.people.find({},{"age":1,"name":1}); { "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" } { "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" } { "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" } > db.people.find({},{"age":1}); { "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18 } { "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28 } { "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38 } > db.people.find({},{"name":1}); { "_id" : ObjectId("501dffc605f64f64b0765c53"), "name" : "tom" } { "_id" : ObjectId("501dffcc05f64f64b0765c54"), "name" : "jimmy" } { "_id" : ObjectId("501dffd005f64f64b0765c55"), "name" : "tim" } > db.people.find({},{"name":1,"_id":0}); { "name" : "tom" } { "name" : "jimmy" } { "name" : "tim" } >
使用find函式第二個引數,對於這個文件我們有這些要注意的:
1》 對於非"_id"的所有鍵,其值要麼同時不等於0(表明要查詢該鍵值對),要麼同時等於0(表明要忽略該鍵值對),否則執行報錯。
2》 對於非“_id”的所有鍵,如果其值全部為0,則此時如果也指明瞭鍵“_id”的情況,則鍵“_id”的值必須為0,否則執行報錯。
3》 對於鍵“_id”,如果不指名其值,則查詢結果中肯定包含這個鍵值對,如果不想包含,可以指明其值為0即可。
綜上,我們總結一下如果需要使用第二個引數,則使用的技巧為:指明所有需要得到的鍵的值為非0的數字,如果還要過濾鍵"_id",則再指明"_id"這個鍵值為0即可。
【查詢條件】
上面提到的查詢條件,都是精確匹配,即“=”多少。MongoDB中的查詢顯然還有更復雜的匹配。比如範圍,OR子句和取反等。我們分別進行介紹。
“$lt”、“$lte”、“$gt”、“$gte” 就是所有的範圍比較操作符,分別對應<、<=、>、>=。將他們組合應用,就可以查詢值在某個範圍內的文件了,比如我們要查詢18-30歲(含)的所有使用者:
> db.people.find({"age":{"$gte":18,"$lte":30}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
>
這種範圍查詢操作符,除用在值為數字的鍵之上外,對於值為日期的鍵的範圍匹配也尤為好用!比如我們要查詢註冊日期在2007/05/01前的使用者資訊,我們可以這樣寫:
> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "reg
istered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "r
egistered" : ISODate("2009-09-02T16:00:00Z") }
> db.user.find({"registered":{"$lte":new Date("2007/05/01")}});
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
>
有時我們需要查詢某個鍵不等於某個值的文件,我們可以使用這個條件操作符"$ne",他表示不相等。如我們要查詢註冊使用者名稱不等於“tom”的文件:
> db.user.find({"name":{"$ne":"tom"}});
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "r
egistered" : ISODate("2009-09-02T16:00:00Z") }
>
條件操作符"$ne"適合於所有型別的值!
【OR查詢】
MongoDB中有兩種方式進行OR查詢:“$in”可以用來查詢一個鍵的多個值,“$or”則更通用一些,可以用來完成多個鍵值對的組合。我們也分別演示一下:我們要查詢獎券號碼為10,20,30 的所有投注者的姓名:
> db.raffle.find({})
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
> db.raffle.find({"ticket_no":{"$in":[10,20,30]}}, {"name":1})
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom" }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy" }
>
但如果還要求我們查詢出獎券號碼為10,20,30 或投注者姓名為“tim”的所有投注資訊,我們單純用"$in"是無法勝任的,我們可以用操作符"$or",“$or”操作符可以組合其他操作符如"$in"拼湊的條件:
> db.raffle.find({})
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
> db.raffle.find({"$or":[{"ticket_no":{"$in":[10,20,30]}},{"name":"tim"}]});
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
>
使用“$or”操作符,其值為一個條件陣列,陣列中各個條件最後通過or組合。使用這個條件操作符有一個最佳實踐是:將最寬鬆的條件放在前面,這樣可以加快文件匹配速度!
【$mod 和 $not】
$mod操作符,使用格式為 {"鍵":{"$mod":[num1, num2]}},查詢“鍵”的值對num1取餘,如果這個值等於num2,則整條文件符合條件。如我們要查詢所有在其本命年的使用者(年齡是12的整數倍):
> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
> db.people.find({"age":{"$mod":[12,0]}});
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
>
$not是元條件符,即可以用於任何其他條件之上的,表明取反,還是上面的例子,我們這次要查所有不在其本命年的使用者文件資訊:
> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
> db.people.find({"age":{"$not":{"$mod":[12,0]}}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
>
$not條件符配合正則表示式使用非常強大,這裡先預報一下,我們後續會提到引入正則表示式的查詢。
【特定於型別的查詢】
查詢某鍵,其值為null的文件時,我們會發現一個奇怪的現象,我們先看一個例子:
> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
> db.cc.find({"y":null});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
>
利用值為null的條件貌似可以查詢正確的文件,但我們忽略瞭如果集合中存在沒有鍵"y"的文件,會出現什麼情況:
> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
> db.cc.find({"y":null});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
>
果然不出意外,沒有這個鍵的文件同樣匹配值為null這種條件,如果我們需要過濾掉這種文件,需要另外一個條件操作符$exists,指明這個鍵必須存在:
> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
> db.cc.find({"y":{"$in":[null],$exists:true}});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
>
我們發現,因為MongoDB中沒有提供類似於"$eq"這種相等的條件操作符,所以“=null”的判斷只能通過{"$in":[null]}來實現!
【正則表示式】
正則表示式在任何語言中都是操作字串的一大利器!在MongoDB的查詢中,其依然威力不減。正則表示式可以靈活的匹配字串型別的值。如我們要查詢所有姓名為“joy”開頭並且忽略大小寫的使用者文件:
> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
> db.people.find({"name":/joe.*/i});
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
>
Shell中寫正則表示式的方式和JavaScript的一致,寫在一對“ // ”之間的就是正則表示式。具體正則表示式的一些寫法大家可以參考正則表示式的相關規範。我們上面提到了操作符$not和正則表示式的聯合使用,我們這邊也演示一下:
> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
> db.people.find({"name":{"$not":/joe.*/i}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
>
我們可以看到使用$not是將其作為正則表示式的鍵,表明和這個正則表示式不匹配。我們在最前邊也介紹了,MongoDB支援正則表示式這種資料型別,即“鍵值對”中允許“值”為正則表示式,對於這種鍵值對,正則表示式也可以匹配成功:
> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
{ "_id" : ObjectId("50210f63d6acd1b2a3fb317d"), "age" : 99, "name" : /joe/i }
> db.people.find({"name":/joe/i});
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
{ "_id" : ObjectId("50210f63d6acd1b2a3fb317d"), "age" : 99, "name" : /joe/i }
>
但注意正則表示式的匹配必須是完全匹配,即正則表示式的寫法完全相同才可以匹配成功(這個在實際應用中基本不會遇見的)。
MongoDB可以為字首型的正則表示式(/^joe/i)查詢使用索引,所以這種字首型的正則表式查詢速度會很快!
這裡再稍微提一下find函式和findOne函式的區別,剛才說了,find函式返回的是一個子集,而findOne就是返回一條文件或null(未查詢到結果),對於一些可以接受文件做引數的函式如insert,可以直接使用findOne的返回值作為引數。
最後就這基本的查詢條件操作符部分稍作總結,因為我們前面介紹修改器操作符也是以"$"開頭,如“$set”,"$inc"等,這裡介紹的查詢條件操作符也是以"$"開頭,那使用上有哪些區分呢?修改器操作符都是外層文件的鍵,而查詢條件的操作符基本都是內層文件的鍵(注意“$or”是一個例外)。