摘要:但有時我們希望返回與查詢條件相匹配的任意一個數組元素。首先,可以使用要求同時使用查詢條件中的兩個語句與一個數組元素進行比較。
上一篇文章:MongoDB指南---7、find簡介與查詢條件
下一篇文章:MongoDB指南---9、游標與數據庫命令
如第2章所述,MongoDB的文檔可以使用多種類型的數據。其中有一些在查詢時會有特別的表現。
4.3.1 nullnull類型的行為有點奇怪。它確實能匹配自身,所以要是有一個包含如下文檔的集合:
> db.c.find() { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null } { "_id" : ObjectId("4ba0f0dfd22aa494fd523622"), "y" : 1 } { "_id" : ObjectId("4ba0f148d22aa494fd523623"), "y" : 2 }
就可以按照預期的方式查詢"y"鍵為null的文檔:
> db.c.find({"y" : null}) { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null }
但是,null不僅會匹配某個鍵的值為null的文檔,而且還會匹配不包含這個鍵的文檔。所以,這種匹配還會返回缺少這個鍵的所有文檔:
> db.c.find({"z" : null}) { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null } { "_id" : ObjectId("4ba0f0dfd22aa494fd523622"), "y" : 1 } { "_id" : ObjectId("4ba0f148d22aa494fd523623"), "y" : 2 }
如果僅想匹配鍵值為null的文檔,既要檢查該鍵的值是否為null,還要通過"$exists"條件判定鍵值已存在:
> db.c.find({"z" : {"$in" : [null], "$exists" : true}})
很遺憾,沒有"$eq"操作符,所以這條查詢語句看上去有些令人費解,但是使用只有一個元素的"$in"操作符效果是一樣的。
4.3.2 正則表達式正則表達式能夠靈活有效地匹配字符串。例如,想要查找所有名為Joe或者joe的用戶,就可以使用正則表達式執行不區分大小寫的匹配:
> db.users.find({"name" : /joe/i})
系統可以接受正則表達式標志(i),但不是一定要有。現在已經匹配了各種大小寫組合形式的joe,如果還希望匹配如"joey"這樣的鍵,可以略微修改一下剛剛的正則表達式:
> db.users.find({"name" : /joey?/i})
MongoDB使用Perl兼容的正則表達式(PCRE)庫來匹配正則表達式,任何PCRE支持的正則表達式語法都能被MongoDB接受。建議在查詢中使用正則表達式前,先在JavaScript shell中檢查一下語法,確保匹配與設想的一致。
MongoDB可以為前綴型正則表達式(比如/^joey/)查詢創建索引,所以這種類型的查詢會非常高效。
正則表達式也可以匹配自身。雖然幾乎沒有人直接將正則表達式插入到數據庫中,但要是萬一你這么做了,也可以匹配到自身:
> db.foo.insert({"bar" : /baz/}) > db.foo.find({"bar" : /baz/}) { "_id" : ObjectId("4b23c3ca7525f35f94b60a2d"), "bar" : /baz/ }4.3.3 查詢數組
查詢數組元素與查詢標量值是一樣的。例如,有一個水果列表,如下所示:
> db.food.insert({"fruit" : ["apple", "banana", "peach"]})
下面的查詢:
> db.food.find({"fruit" : "banana"})
會成功匹配該文檔。這個查詢好比我們對一個這樣的(不合法)文檔進行查詢:{"fruit" : "apple", "fruit" : "banana", "fruit" : "peach"}。
1. $all如果需要通過多個元素來匹配數組,就要用"$all"了。這樣就會匹配一組元素。例如,假設創建了一個包含3個元素的集合:
> db.food.insert({"_id" : 1, "fruit" : ["apple", "banana", "peach"]}) > db.food.insert({"_id" : 2, "fruit" : ["apple", "kumquat", "orange"]}) > db.food.insert({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})
要找到既有"apple"又有"banana"的文檔,可以使用"$all"來查詢:
> db.food.find({fruit : {$all : ["apple", "banana"]}}) {"_id" : 1, "fruit" : ["apple", "banana", "peach"]} {"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}
這里的順序無關緊要。注意,第二個結果中"banana"在"apple"之前。要是對只有一個元素的數組使用"$all",就和不用"$all"一樣了。例如,{fruit : {$all : ["apple"]}和{fruit : "apple"}的查詢結果完全一樣。
也可以使用整個數組進行精確匹配。但是,精確匹配對于缺少元素或者元素冗余的情況就不大靈了。例如,下面的方法會匹配之前的第一個文檔:
> db.food.find({"fruit" : ["apple", "banana", "peach"]})
但是下面這個就不會匹配:
> db.food.find({"fruit" : ["apple", "banana"]})
這個也不會匹配:
> db.food.find({"fruit" : ["banana", "apple", "peach"]})
要是想查詢數組特定位置的元素,需使用key.index語法指定下標:
> db.food.find({"fruit.2" : "peach"})
數組下標都是從0開始的,所以上面的表達式會用數組的第3個元素和"peach"進行匹配。
2. $size"$size"對于查詢數組來說也是非常有用的,顧名思義,可以用它查詢特定長度的數組。例如:
> db.food.find({"fruit" : {"$size" : 3}})
得到一個長度范圍內的文檔是一種常見的查詢。"$size"并不能與其他查詢條件(比如"$gt")組合使用,但是這種查詢可以通過在文檔中添加一個"size"鍵的方式來實現。這樣每一次向指定數組添加元素時,同時增加"size"的值。比如,原本這樣的更新:
> db.food.update(criteria, {"$push" : {"fruit" : "strawberry"}})
就要變成下面這樣:
> db.food.update(criteria, ... {"$push" : {"fruit" : "strawberry"}, "$inc" : {"size" : 1}})
自增操作的速度非常快,所以對性能的影響微乎其微。這樣存儲文檔后,就可以像下面這樣查詢了:
> db.food.find({"size" : {"$gt" : 3}})
很遺憾,這種技巧并不能與"$addToSet"操作符同時使用。
3. $slice操作符本章前面已經提及,find的第二個參數是可選的,可以指定需要返回的鍵。這個特別的"$slice"操作符可以返回某個鍵匹配的數組元素的一個子集。
例如,假設現在有一個博客文章的文檔,我們希望返回前10條評論,可以這樣做:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : 10}})
也可以返回后10條評論,只要在查詢條件中使用-10就可以了:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -10}})
"$slice"也可以指定偏移值以及希望返回的元素數量,來返回元素集合中間位置的某些結果:
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : [23, 10]}})
這個操作會跳過前23個元素,返回第24~33個元素。如果數組不夠33個元素,則返回第23個元素后面的所有元素。
除非特別聲明,否則使用"$slice"時將返回文檔中的所有鍵。別的鍵說明符都是默認不返回未提及的鍵,這點與"$slice"不太一樣。例如,有如下博客文章文檔:
{ "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "...", "comments" : [ { "name" : "joe", "email" : "joe@example.com", "content" : "nice post." }, { "name" : "bob", "email" : "bob@example.com", "content" : "good post." } ] }
用"$slice"來獲取最后一條評論,可以這樣:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -1}}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "...", "comments" : [ { "name" : "bob", "email" : "bob@example.com", "content" : "good post." } ] }
"title"和"content"都返回了,即便是并沒有顯式地出現在鍵說明符中。
4. 返回一個匹配的數組元素如果知道元素的下標,那么"$slice"非常有用。但有時我們希望返回與查詢條件相匹配的任意一個數組元素。可以使用$操作符得到一個匹配的元素。對于上面的博客文章示例,可以用如下的方式得到Bob的評論:
> db.blog.posts.find({"comments.name" : "bob"}, {"comments.$" : 1}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "comments" : [ { "name" : "bob", "email" : "bob@example.com", "content" : "good post." } ] }
注意,這樣只會返回第一個匹配的文檔。如果Bob在這篇博客文章下寫過多條評論,只有"comments"數組中的第一條評論會被返回。
5. 數組和范圍查詢的相互作用文檔中的標量(非數組元素)必須與查詢條件中的每一條語句相匹配。例如,如果使用{"x" : {"$gt" : 10, "$lt" : 20}}進行查詢,只會匹配"x"鍵的值大于等于10并且小于等于20的文檔。但是,假如某個文檔的"x"字段是一個數組,如果"x"鍵的某一個元素與查詢條件的任意一條語句相匹配(查詢條件中的每條語句可以匹配不同的數組元素),那么這個文檔也會被返回。
下面用一個例子來詳細說明這種情況。假如有如下所示的文檔:
{"x" : 5} {"x" : 15} {"x" : 25} {"x" : [5, 25]}
如果希望找到"x"鍵的值位于10和20之間的所有文檔,直接想到的查詢方式是使用db.test.find({"x" : {"$gt" : 10, "$lt" : 20}}),希望這個查詢的返回文檔是{"x" : 15}。但是,實際返回了兩個文檔:
> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}}) {"x" : 15} {"x" : [5, 25]}
5和25都不位于10和20之間,但是這個文檔也返回了,因為25與查詢條件中的第一個語句(大于10)相匹配,5與查詢條件中的第二個語句(小于20)相匹配。
這使對數組使用范圍查詢沒有用:范圍會匹配任意多元素數組。有幾種方式可以得到預期的行為。
首先,可以使用"$elemMatch"要求MongoDB同時使用查詢條件中的兩個語句與一個數組元素進行比較。但是,這里有一個問題,"$elemMatch"不會匹配非數組元素:
> db.test.find({"x" : {"$elemMatch" : {"$gt" : 10, "$lt" : 20}}) > // 查不到任何結果
{"x" : 15}這個文檔與查詢條件不再匹配了,因為它的"x"字段是個數組。
如果當前查詢的字段上創建過索引(第5章會講述索引相關內容),可以使用min()和max()將查詢條件遍歷的索引范圍限制為"$gt"和"$lt"的值:
> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}).min({"x" : 10}).max({"x" : 20}) {"x" : 15}
現在,這個查詢只會遍歷值位于10和20之間的索引,不再與5和25進行比較。只有當前查詢的字段上建立過索引時,才可以使用min()和max(),而且,必須為這個索引的所有字段指定min()和max()。
在可能包含數組的文檔上應用范圍查詢時,使用min()和max()是非常好的:如果在整個索引范圍內對數組使用"$gt"/"$lt"查詢,效率是非常低的。查詢條件會與所有值進行比較,會查詢每一個索引,而不僅僅是指定索引范圍內的值。
有兩種方法可以查詢內嵌文檔:查詢整個文檔,或者只針對其鍵/值對進行查詢。
查詢整個內嵌文檔與普通查詢完全相同。例如,有如下文檔:
{ "name" : { "first" : "Joe", "last" : "Schmoe" }, "age" : 45 }
要查尋姓名為Joe Schmoe的人可以這樣:
> db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}})
但是,如果要查詢一個完整的子文檔,那么子文檔必須精確匹配。如果Joe決定添加一個代表中間名的鍵,這個查詢就不再可行了,因為查詢條件不再與整個內嵌文檔相匹配。而且這種查詢還是與順序相關的,{"last" : "Schmoe","first" : "Joe"}什么都匹配不到。
如果允許的話,通常只針對內嵌文檔的特定鍵值進行查詢,這是比較好的做法。這樣,即便數據模式改變,也不會導致所有查詢因為要精確匹配而一下子都掛掉。我們可以使用點表示法查詢內嵌文檔的鍵:
> db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"})
現在,如果Joe增加了更多的鍵,這個查詢依然會匹配他的姓和名。
這種點表示法是查詢文檔區別于其他文檔的主要特點。查詢文檔可以包含點來表達“進入內嵌文檔內部”的意思。點表示法也是待插入的文檔不能包含“.”的原因。將URL作為鍵保存時經常會遇到此類問題。一種解決方法就是在插入前或者提取后執行一個全局替換,將“.”替換成一個URL中的非法字符。
當文檔結構變得更加復雜以后,內嵌文檔的匹配需要些許技巧。例如,假設有博客文章若干,要找到由Joe發表的5分以上的評論。博客文章的結構如下例所示:
> db.blog.find() { "content" : "...", "comments" : [ { "author" : "joe", "score" : 3, "comment" : "nice post" }, { "author" : "mary", "score" : 6, "comment" : "terrible post" } ] }
不能直接用db.blog.find({"comments" : {"author" : "joe","score" : {"$gte" : 5}}})來查尋。內嵌文檔的匹配,必須要整個文檔完全匹配,而這個查詢不會匹配"comment"鍵。使用db.blog.find({"comments.author" : "joe","comments.score" : {"$gte" : 5}}也不行,因為符合author條件的評論和符合score條件的評論可能不是同一條評論。也就是說,會返回剛才顯示的那個文檔。因為"author" : "joe"在第一條評論中匹配了,"score" : 6在第二條評論中匹配了。
要正確地指定一組條件,而不必指定每個鍵,就需要使用"$elemMatch"。這種模糊的命名條件句能用來在查詢條件中部分指定匹配數組中的單個內嵌文檔。所以正確的寫法應該是下面這樣的:
> db.blog.find({"comments" : {"$elemMatch" : {"author" : "joe", "score" : {"$gte" : 5}}}})
"$elemMatch"將限定條件進行分組,僅當需要對一個內嵌文檔的多個鍵操作時才會用到。
4.4 $where查詢鍵/值對是一種表達能力非常好的查詢方式,但是依然有些需求它無法表達。其他方法都敗下陣時,就輪到"$where"子句登場了,用它可以在查詢中執行任意的JavaScript。這樣就能在查詢中做(幾乎)任何事情。為安全起見,應該嚴格限制或者消除"$where"語句的使用。應該禁止終端用戶使用任意的"$where"語句。
"$where"語句最常見的應用就是比較文檔中的兩個鍵的值是否相等。假如我們有如下文檔:
> db.foo.insert({"apple" : 1, "banana" : 6, "peach" : 3}) > db.foo.insert({"apple" : 8, "spinach" : 4, "watermelon" : 4})
我們希望返回兩個鍵具有相同值的文檔。第二個文檔中,"spinach"和"watermelon"的值相同,所以需要返回該文檔。MongoDB似乎從來沒有提供過一個$條件語句來做這種查詢,所以只能用"$where"子句借助JavaScript來完成了:
> db.foo.find({"$where" : function () { ... for (var current in this) { ... for (var other in this) { ... if (current != other && this[current] == this[other]) { ... return true; ... } ... } ... } ... return false; ... }});
如果函數返回true,文檔就做為結果集的一部分返回;如果為false,就不返回。
不是非常必要時,一定要避免使用"$where"查詢,因為它們在速度上要比常規查詢慢很多。每個文檔都要從BSON轉換成JavaScript對象,然后通過"$where"表達式來運行。而且"$where"語句不能使用索引,所以只在走投無路時才考慮"$where"這種用法。先使用常規查詢進行過濾,然后再使用"$where"語句,這樣組合使用可以降低性能損失。如果可能的話,使用"$where"語句前應該先使用索引進行過濾,"$where"只用于對結果進行進一步過濾。
進行復雜查詢的另一種方法是使用聚合工具,第7章會詳細介紹。
在服務器上執行JavaScript時必須注意安全性。如果使用不當,服務器端JavaScript很容易受到注入攻擊,與關系型數據庫中的注入攻擊類似。不過,只要在接受輸入時遵循一些規則,就可以安全地使用JavaScript。也可以在運行mongod時指定--noscripting選項,完全關閉JavaScript的執行。
JavaScript的安全問題都與用戶在服務器上提供的程序相關。如果希望避免這些風險,那么就要確保不能直接將用戶輸入的內容傳遞給mongod。例如,假如你希望打印一句“Hello, name!”,這里的name是由用戶提供的。使用如下所示的JavaScript函數是非常容易想到的:
> func = "function() { print("Hello, "+name+"!"); }"
如果這里的name是一個用戶定義的變量,它可能會是""); db.dropDatabase();print(""這樣一個字符串,因此,上面的代碼會被轉換成如下代碼:
> func = "function() { print("Hello, "); db.dropDatabase(); print("!"); }"
如果執行這段代碼,你的整個數據庫就會被刪除!
為了避免這種情況,應該使用作用域來傳遞name的值。以Python為例:
func = pymongo.code.Code("function() { print("Hello, "+username+"!"); }", {"username": name})
現在,數據庫會輸出如下的內容,不會有任何風險:
Hello, "); db.dropDatabase(); print("!
由于代碼實際上可能是字符串和作用域的混合體,所以大多數驅動程序都有一種特殊類型,用于向數據庫傳遞代碼。作用域是用于表示變量名和值的映射的文檔。對于要被執行的JavaScript函數來說,這個映射就是一個局部作用域。因此,在上面的例子中,函數可以訪問username這個變量,這個變量的值就是用戶傳進來的字符串。
shell中沒有包含作用域的代碼類型,所以作用域只能在字符串或者JavaScript函數中使用。
上一篇文章:MongoDB指南---7、find簡介與查詢條件
下一篇文章:MongoDB指南---9、游標與數據庫命令
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/19557.html
摘要:上一篇文章指南更新文檔下一篇文章指南特定類型的查詢本章將詳細介紹查詢。查詢條件和就是全部的比較操作符,分別對應和。如果查詢優化器可以更高效地處理,那就選擇使用它。注意,查詢優化器不會對進行優化,這與其他操作符不同。 上一篇文章:MongoDB指南---6、更新文檔下一篇文章:MongoDB指南---8、特定類型的查詢 本章將詳細介紹查詢。主要會涵蓋以下幾個方面: 使用find或者f...
摘要:上一篇文章指南更新文檔下一篇文章指南特定類型的查詢本章將詳細介紹查詢。查詢條件和就是全部的比較操作符,分別對應和。如果查詢優化器可以更高效地處理,那就選擇使用它。注意,查詢優化器不會對進行優化,這與其他操作符不同。 上一篇文章:MongoDB指南---6、更新文檔下一篇文章:MongoDB指南---8、特定類型的查詢 本章將詳細介紹查詢。主要會涵蓋以下幾個方面: 使用find或者f...
摘要:但有時我們希望返回與查詢條件相匹配的任意一個數組元素。首先,可以使用要求同時使用查詢條件中的兩個語句與一個數組元素進行比較。 上一篇文章:MongoDB指南---7、find簡介與查詢條件下一篇文章:MongoDB指南---9、游標與數據庫命令 如第2章所述,MongoDB的文檔可以使用多種類型的數據。其中有一些在查詢時會有特別的表現。 4.3.1 null null類型的行為有點奇...
摘要:上一篇文章指南簡介下一篇文章指南基礎知識數據類型非常強大但很容易上手。把同種類型的文檔放在一個集合里,數據會更加集中。命名集合使用名稱進行標識。集合名不能是空字符串。簡單起見,數據庫名應全部小寫。 上一篇文章:MongoDB指南---1、MongoDB簡介下一篇文章:MongoDB指南---3、MongoDB基礎知識-數據類型 MongoDB非常強大但很容易上手。本章會介紹一些Mon...
摘要:上一篇文章指南簡介下一篇文章指南基礎知識數據類型非常強大但很容易上手。把同種類型的文檔放在一個集合里,數據會更加集中。命名集合使用名稱進行標識。集合名不能是空字符串。簡單起見,數據庫名應全部小寫。 上一篇文章:MongoDB指南---1、MongoDB簡介下一篇文章:MongoDB指南---3、MongoDB基礎知識-數據類型 MongoDB非常強大但很容易上手。本章會介紹一些Mon...
閱讀 796·2021-09-22 16:01
閱讀 2093·2021-08-20 09:37
閱讀 1696·2019-08-30 15:54
閱讀 1696·2019-08-30 15:44
閱讀 835·2019-08-28 18:23
閱讀 3017·2019-08-26 12:17
閱讀 1021·2019-08-26 11:56
閱讀 1544·2019-08-23 16:20