Mongodb查询

2018/03/05 Mongodb

MongoDB查询

查找文档无疑是最常见的MongoDB数据库操作。

如何访问MongoDB服务器中的文档。

  • 理解Cursor对象的用途;
  • 使用Cursor对象访问集合中的文档;
  • 查找单个文档;
  • 查找多个文档;
  • 使用查询运算符根据字段值查找文档;
  • 根据子文档字段查找文档。
  • 确定与查找操作匹配的文档数;
  • 以特定顺序返回文档;
  • 限制返回的文档数;
  • 大型数据集分页;
  • 减少返回的文档包含的字段;
  • 查找数据集中特定字段的不同值。
  • 根据字段值将文档分组;
  • 创建聚合流水线(aggregation pipeline);
  • 使用聚合流水线来操作结果;
  • 创建包含reduce和finalize函数的映射-归并(map reduce)操作;
  • 使用映射-归并将一组文档归并为特定形式。

    理解Cursor对象

    在MongoDB shell中对Collection对象执行有些操作时,结果是以Cursor对象的方式返回的。Cursor对象相当于一个指针,可通过迭代它来访问数据库中的一组对象。例如,当您使用find()时,返回的并非实际文档,而是一个Cursor对象,您可以使用它来读取结果中的文档。

由于Cursor对象是可以迭代的,因此在内部存储了一个指向当前位置的索引,这让您能够每次读取一个文档。别忘了,有些操作只影响Cursor中的当前文档,并将索引加1;而有些操作影响当前索引之后的所有文档。 列出了可调用Cursor对象的基本方法。这些方法让您能够操作Cursor对象,并控制对其表示的实际对象的访问。

  • batchSize(size) 指定MongoDB在每个网络响应中向客户端返回的文档数,默认为20
  • count() 返回Cursor对象表示的文档数
  • explain() 返回一个文档,它描述了将用来返回查询结果的查询执行计划,包括要使用的索引。这提供了一种排除缓慢查询故障或优化请求的极佳方式
  • forEach(function) 迭代Cursor对象中的每个文档,并对其执行指定的JavaScript函数。指定的JavaScript函数必须将文档作为唯一的参数。下面是一个示例: myColl.find().forEach(function(doc){ print(“name: “ + doc.name); });
  • hasNext() 在使用next()迭代游标时使用。如果游标中还有其他文档,可以继续迭代,则返回true
  • hint(index) 强制MongoDB使用一个或多个特定的索引进行查询。index可以是字符串,如hint(“myIndex_1”);也可以是文档,其中的属性为索引名,而值为1,如hint({ myIndex: 1})
  • limit(maxItems) 将游标的结果集限制为maxItems指定的大小
  • map(function) 迭代Cursor中的每个文档,并对其执行指定的函数。将每次迭代的返回值都加入到一个数组中,并返回这个数组。例如: names = myColl.find().map(function(doc){ return doc.name; });
  • max(indexBounds) 指定Cursor返回的文档中字段的最大值,例如: max({ height: 60, age: 10 })
  • min() 指定Cursor返回的文档中字段的最小值,例如: min({ height: 60, age: 10 })
  • next() 从Cursor返回下一个文档,并将迭代索引加1
  • objsLeftInBatch() 指出Cursor返回的当前那批文档中还余下多少个。迭代到最前那批文档中最后一个后,需要再次向服务器发出请求以取回下批文档
  • readPref(mode, tagSet) 指定Cursor的读取首选项,以控制客户端向副本集发送查询的方式
  • size() 在执行方法skip()和limit()后,返回Cursor中的文档数
  • skip(n) 返回另一个Cursor,它从n个文档后开始返回结果
  • snapshot() 强制Cursor使用根据_id字段创建的索引,确保Cursor只返回每个文档一次
  • sort(sortObj) 将结果按sortObj指定的方式排序。sortObj应包含用于排序的字段,并使用-1指定降序或使用1指定升序,例如: sort({ name: 1, age: -1 })
  • toArray() 返回一个JavaScript对象数组,表示Cursor返回的所有文档。

理解查询运算符

在使用Collection对象查找和修改文档的操作中,有些操作允许您指定query参数。query参数对Cursor对象中返回的文档进行限制,使其值包含满足指定条件的文档。 query参数是标准的JavaScript对象,但使用了MongoDB shell和服务器都能明白的特殊属性名。query参数的属性称为运算符,因为它们对数据进行运算,以确定文档是否应包含在结果集中。这些运算符判断文档中字段的值是否符合指定条件。 例如,要查找所有字段count大于10且字段name为test的文档,可使用这样的query对象:

{count:{$gt:10}, name:'test'}

运算符$gt指定字段count大于10的文档。name:’test’使用了标准冒号语法,它指定字段name必须为test。注意到上述query对象包含多个运算符。在同一个查询中,可包含多个不同的运算符。 在query对象中指定字段时,可使用句点表示法来指定子文档的字段。例如,如果用户文档的格式如下:

{
name:"test",
stats: { height:74, eyes:'blue'}
}

则可使用下面的query对象来查询眼睛为蓝色的用户:

{stats.eyes:'blue'}

一些较常用的查询运算符。 决定返回的结果集的查询运算符

  • field:value 与字段值为value的文档匹配,例如: {name:”myName”}
  • $gt 与字段值大于指定值的文档匹配,例如: {size:{$gt:5}}
  • $gte 与字段值大于等于指定值的文档匹配,例如: {size:{$gte:5}}
  • $in 与字段值包含在指定数组中的文档匹配,例如: {name:{$in:[‘item1’, ‘item2’] } }
  • $lt 与字段值小于指定值的文档匹配,例如: {size:{$lt:5}}
  • $lte 与字段值小于等于指定值的文档匹配,例如: {size:{$lte:5}}
  • $ne 与字段值不等于指定值的文档匹配,例如: {name:{$ne:”badName”}}
  • $nin 与字段值不包含在指定数组中的文档匹配,例如: {name:{$in:[‘item1’, ‘item2’] } }
  • $or 使用逻辑或连接查询子句,并返回符合任何一个子句条件的文档,例如: {$or:[{size:{$lt:5}},{size:{$gt:10} } ] }
  • $and 使用逻辑与连接查询子句,并返回与两个子句条件都匹配的文档,例如: {$and:[{size:{$lt:5}},{size:{$gt:10} } ] }
  • $not 反转查询表达式的效果,返回与查询表达式不匹配的文档,例如: {$not:{size:{$lt:5} } } }
  • $nor 使用逻辑或非连接查询子句,返回与两个子句都不匹配的文档,例如: {$nor:{size:{$lt:5}},{name:”myName”} } }
  • $exists 匹配包含指定字段的文档,例如: {specialField:{$exists:true}}
  • $type 匹配指定字段为指定BSON类型(第1章的表1.1列出了BSON类型的编号)的文档,例如: {specialField:{$type:} }
  • $mod 对执行字段执行求模运算,并返回结果为指定值的文档。求模运算条件是使用数组指定的,其中第一个数字为除数,第二个数组为余数。例如: {number:{$mod:[2,0] } }
  • $regex 返回指定字段的值与指定正则表达式匹配的文档,例如: {myString:{$regex:’some.*exp’} }
  • $all 返回这样的文档,即其指定数组字段包含所有指定的元素,例如: {myArr:{$all:[‘one’,’two’,’three] } }
  • $elemMatch 返回这样的文档,即其指定的数组字段至少有一个元素与指定的条件都匹配,例如: {myArr:{$elemMatch:{value:{$gt:5},size:{$lt:3} } } }
  • $size 返回这样的文档,即其指定的数组字段为指定的长度,例如:{myArr:{$size:5} }

从集合中获取文档

最常见的MongoDB数据库操作之一是检索一个或多个文档。例如,来看电子商务网站存储的商品信息,这些信息只存储一次,但被检索很多次。 数据检索好像很简单,但需要过滤、排序、显示和聚合结果时,可能变得非常复杂。 来看find()和findOne()的语法:

findOne(query, projection)
find(query, projection)

find()和findOne()的第一个参数都是一个query对象,该对象包含查询运算符,指定了文档的字段需要满足的条件。结果集只包含与查询条件匹配的文档。参数projection是一个这样的对象,即指定返回的文档应包含哪些字段。 方法find()和findOne()的差别在于,find()返回一个find()Cursor对象,表示与查询条件匹配的文档,而findOne返回与查询条件匹配的第一个文档。

查找特定的文档

知道如何使用方法find()和findOne()来访问集合中的多个文档后,您将发现自己想查找特定的文档。对结果集进行限制,使其只包含特定文档有多个优点,其中包括:

  • 需要的网络流量更少;
  • 客户端和服务器使用的内存更少;
  • 客户端为查找重要数据需要做的处理更少。
  • 要在方法find()限制返回的文档,可指定一个query对象,对Cursor中的返回文档进行限制。

    根据特定的字段值查找文档

    限制结果的最基本方式是,在查询文档中指定必须匹配的字段值,这样将只返回指定字段为指定值的文档。 例如,要获取数据库words中长度为5的单词,可使用类似于下面的查询:

    find({size: 5});
    

    同样,要从该数据库中获取单词there,可使用下面的查询:

    find({word: "there"});
    

    ### 根据字段值数组查找文档 要查询特定字段为多个值之一的文档,可使用运算符$in。对于数据库words,一个这样的典型示例是,根据字段first查找以a、b或c打头的单词,如下所示:

    find({first:{$in: ['a', 'b', 'c'] } });
    

    根据字段值的大小查找文档

    另一种常见的查询是,根据字段是否大于或小于指定的值来查询文档。运算符$gt和$lt让您能够指定一个字段和一个值,并在结果集中包含这样的文档,即其指定字段大于或小于指定的值。 下面的示例查找指定字段大于指定值的文档:

    find({size:{$gt: 12}});
    

    下面的示例查找指定字段小于指定值的文档:

    find({size:{$lt: 12}});
    

    根据数组字段的长度查找文档

    另一种常见的查询是,查找指定数组字段为指定长度的文档,为此可使用运算符$size。 例如,下面的查询查找数组字段letters包含12个字母的单词:

    find({letters:{$size: 12}});
    

    同样,下面的查询查找数组字段letters长于10的单词:

    find({letters:{$size: {$gt: 10}}});
    

    根据子文档中的值查找文档

    您可能还需要根据子文档包含的值来查询文档,为此只需使用句点语法来引用子文档的字段。例如,在数据库words中,可像下面这样指定子文档stats的字段vowels需满足的条件:

    {"stats.vowels":{$gt:6}}
    

    根据数组字段的内容查找文档

    另一种很有用的查询是,根据数组字段的内容查找文档。运算符$all匹配这样的文档:其指定数组字段包含查询数组中所有的元素。 例如,下面的查询在数据库words中查找包含全部5个元音字母的单词:

    {letters:{$all: ['a','e','i','o','u'] } }
    

    根据字段是否存在查找文档

    鉴于MongoDB不要求文档遵循结构化模式,因此可能出现这样的情况:某个字段在有些文档中有,而在其他文档中没有。在这种这种情况下,运算符$exists很有用,它让您能够对结果集进行限制,使其只包含有或者没有指定字段的文档。 例如,在数据库words中,不包含非字母字符的单词没有字段otherChars。下面的查询查找包含非字母字符的单词:

    {otherChars: {$exists:true}}
    

    根据子文档数组中的字段查找文档

    在MongoDB文档模型中,一种比较棘手的查询是,根据数组字段中的子文档来查找文档。在这种情况下,文档包含一个子文档数组,而您要根据子文档中的字段来查询文档。 要根据数组字段中的子文档进行查询,可使用运算符$elemMatch。这个运算符让您能够根据数组中的子文档进行查询。 为演示运算符$elemMatch,最简单的方式是使用数据库words中的子文档数组字段charsets。字段charsets是一个文档数组,其结构如下:

    {
    type: <string>,
    chars: <array>
    }
    

    下面的查询匹配这样的文档,即其字段charsets包含这样的文档:字段type为other,而数组字段chars的长度为2:

    {charsets:{$elemMatch: {$and: [{type: 'other'},{chars: {$size: 2}}] } }}
    

计算文档数

访问MongoDB中的文档集时,您可能想在取回文档前获悉有多少个文档。在MongoDB服务器和客户端计算文档数的开销很小,因为不需要实际传输文档。 对find()返回的文档集执行操作时,也应明了将要处理的文档有多少,在大型环境中尤其如此。有时候,您只想知道文档数。例如,如果您要获悉应用程序中配置了多少用户,只需计算集合users中有多少个文档。 Cursor对象的方法count()指出它表示多少个文档。例如,下面的代码使用方法find()获取一个Cursor对象,再使用方法count()获取文档数:

cursor = wordsColl.find({first: {$in: ['a', 'b', 'c']}});
itemCount = cursor.count();

itemCount的值为与find()查询匹配的单词数。

对结果集进行排序

从MongoDB数据库检索文档时,一个重要方面是对找到的文档进行排序。在只想取回特定数量的文档(如前10个)或要对结果集进行分页时,这特别有用。Options对象提供了sort选项,让您能够指定用于排序的文档字段和方向。 Cursor对象的方法sort()让您能够指定要根据哪些字段对游标中的文档进行排序,并按相应的顺序返回文档。方法sort()将一个对象作为参数,这个对象将字段名用作属性名,并使用值1(升序)和-1(降序)来指定排序顺序。 例如,要按字段name降序排列,可使用下面的代码:

myCollection.find().sort({name:1}).sort({value:-1});

对于同一个游标,可多次使用sort(),从而依次按不同的字段进行排序。例如,要首先按字段name升序排列,再按字段value降序排列,可使用下面的代码:

myCollection.find().sort({name:1}).sort({value:-1});

限制结果集

在大型系统上查询较复杂的文档时,常常需要限制返回的内容,以降低对服务器和客户端网络和内存的影响。要限制与查询匹配的结果集,方法有三种:只接受一定数量的文档;限制返回的字段;对结果分页,批量地获取它们。

限制结果集的大小

要限制find()或其他查询请求返回的数据量,最简单的方法是对find()操作返回的Cursor对象调用方法limit(),它让Cursor对象返回指定数量的文档,可避免检索的对象量超过应用程序的处理能力。 例如,下面的代码只显示集合中的前10个文档,即便匹配的文档有数千个:

cursor = wordsColl.find();
cursor = cursor.limit(10);
cursor.forEach(function(word){
printjson(word);
});

限制返回的字段

为限制文档检索时返回的数据量,另一种极有效的方式是限制要返回的字段。文档可能有很多字段在有些情况下很有用,但在其他情况下没用。从MongoDB服务器检索文档时,需考虑应包含哪些字段,并只请求必要的字段。 要对find()操作从服务器返回的字段进行限制,可在find()操作中使用projection参数。projection参数是一个将字段名用作属性的JavaScript对象,让您能够包含或排除字段:将属性值设置为0/false表示排除;将属性值设置为1/true表示包含。然而,在同一个表达式中,不能同时指定包含和排除。 例如,返回first字段为t的文档时,要排除字段stats、value和comments,可使用下面的projection参数:

find({first:"t"}, {stats:false, value:false, comments:false});

仅包含所需的字段通常更容易。例如,如果只想返回first字段为t的文档的word和size字段,可使用下面的代码:

find({first:"t"}, {word:1, size:1 });

结果集分页

为减少返回的文档数,一种常见的方法是进行分页。要进行分页,需要指定要在结果集中跳过的文档数,还需限制返回的文档数。跳过的文档数将不断增加,每次的增量都是前一次返回的文档数。 要对一组文档进行分页,需要使用Cursor对象的方法limit()和skip()。方法skip()让您能够指定在返回文档前要跳过多少个文档。 每次获取下一组文档时,都增大方法skip()中指定的值,增量为前一次调用limit()时指定的值,这样就实现了数据集分页。 对大型数据集(尤其是从网站获得的数据集)进行分页时,需要使用find()操作为每一页创建一个游标。请不要为等待后续Web请求的到来而让游标长时间打开,这不是什么好主意,因为可能根本没有后续Web请求。 例如,下面的语句依次查找第1~10个文档、第11~20个文档和第21~30个文档:

cursor = collection.find().sort({name:1});
cursor.limit(10);
cursor.skip(0);
cursor = collection.find().sort({name:1});
cursor.limit(10);
cursor.skip(10);
cursor = collection.find().sort({name:1});
cursor.limit(10);
cursor.skip(20);

对数据进行分页时,务必调用方法sort()来确保数据的排列顺序不变。

查找不同的字段值

一种很有用的MongoDB集合查询是,获取一组文档中某个字段的不同值列表。不同(distinct)意味着纵然有数千个文档,您只想知道那些独一无二的值。 Collection对象的方法distinct()让您能够找出指定字段的不同值列表,这种方法的语法如下:

distinct(key, [query])

其中参数key是一个字符串,指定了要获取哪个字段的不同值。要获取子文档中字段的不同值,可使用句点语法,如stats.count。参数query是一个包含标准查询选项的对象,指定了要从哪些文档中获取不同的字段值。 例如,假设有一些包含字段first、last和age的用户文档,要获取年龄超过65岁的用户的不同姓,可使用下面的操作:

lastNames =myUsers.distinct('last', { age: { $gt: 65} } );

方法distinct()返回一个数组,其中包含指定字段的不同值,例如:

["Smith", "Jones", ...]

在MongoDB shell中对查找操作的结果进行分组

对大型数据集执行操作时,根据文档的一个或多个字段的值将结果分组通常很有用。这也可以在取回文档后使用代码来完成,但让MongoDB服务器在原本就要迭代文档的请求中这样做,效率要高得多。 要将查询结果分组,可使用Collection对象的方法group()。分组请求首先收集所有与查询匹配的文档,再对于指定键的每个不同值,都在数组中添加一个分组对象,对这些分组对象执行操作,并返回这个分组对象数组。方法group()的语法如下:

group({key, reduce, initial, [keyf], [cond], finalize})

下面描述了方法group()的参数。

  • keys:一个指定要根据哪些键进行分组的对象,其属性为要用于分组的字段。例如,要根据文档的字段first和last进行分组,可使用{key: {first: 1, last: 1}}。
  • cond:可选参数。这是一个query对象,决定了初始结果集将包含哪些文档。例如,要包含字段size的值大于5的文档,可使用{cond: {size: {$gt: 5}}。
  • initial:一个包含初始字段和初始值的初始group对象,用于在分组期间聚合数据。对于每组不同的键值,都将创建一个初始对象。最常见的情况是,使用一个计数器来跟踪与键值匹配的文档数。例如:{initial: {“count”: 0}}。
  • reduce:一个接受参数obj和prev的函数(function(obj, prev))。对于每个与查询匹配的文档,都执行这个函数。其中参数obj为当前文档,而prev是根据参数initial创建的对象。这让您能够根据obj来更新prev,如计数或累计。例如,要将计数递增,可使用{reduce: function(obj, prev) { prev.count++; }}。
  • finalize:一个接受唯一参数obj的函数(function(obj)),这个参数是对与每个键值组合匹配的最后一个文档执行reduce函数得到的。对于每个键值组合,都将对其使用reduce函数得到的最终对象调用这个函数,然后以数组的方式返回结果。
  • keyf:可选参数,用于替代参数key。可以不指定其属性为分组字段的对象,而指定一个函数,这个函数返回一个用于分组的key对象。这让您能够使用函数动态地指定要根据哪些键进行分组。

从MongoDB shell发出请求时使用聚合来操作数据 MongoDB的一大优点是,能够将数据库查询结果聚合成完全不同于原始集合的结构。MongoDB聚合框架相当杰出,简化了使用一系列操作来处理数据,以生成非凡结果的流程。

聚合指的是在MongoDB服务器中对文档执行一系列操作来生成结果集,其效率比在应用程序中检索并处理文档高得多,因为大量数据将由MongoDB服务器在本地处理。

接下来的几小节将介绍MongoDB聚合框架以及如何在MongoDB shell中使用它。

9.2.1 理解方法aggregate() Collection对象提供了对数据执行聚合操作的方法aggregate(),这个方法的语法如下:

aggregate(operator, [operator], […])

参数operator是一系列聚合运算符(如表9.1所示),让您能够指定要在流水线的各个阶段对数据执行哪种聚合操作。执行第一个运算符后,结果将传递给下一个运算符,后者对数据进行处理并将结果传递给下一个运算符,这个过程不断重复,直到到达流水线末尾。

在MongoDB 2.4和更早的版本中,方法aggregate()返回一个对象,该对象有一个名为result的属性,是一个包含聚合结果的迭代器。这意味着使用2.4版的MongoDB shell时,需要使用类似于下面的代码来访问聚合结果:

results = myCollection.aggregate( …); results.result.forEach( function(item){ … });

在更高的MongoDB版本中,方法aggregate()直接返回一个包含聚合结果的迭代器。这意味着使用2.6和更高版本的MongoDB shell时,需要使用类似于下面的代码来访问聚合结果:

results = myCollection.aggregate( …); results.forEach( function(item){ … });

程序清单9.3所示的示例针对的是MongoDB 2.4。

9.2.2 使用聚合框架运算符 MongoDB提供的聚合框架功能极其强大,让您能够反复将一个聚合运算符的结果传递给另一个运算符。为演示这一点,假设有下面的数据集:

{o_id:”A”, value:50, type:”X”} {o_id:”A”, value:75, type:”X”} {o_id:”B”, value:80, type:”X”} {o_id:”C”, value:45, type:”Y”}

下面的一系列聚合运算符将$match的结果交给运算符$group,再将分组集作为参数results传递给回调函数。注意到引用文档的字段值时,在字段名前加上了美元符号,如$o_id和$value。这种语法让聚合框架将其视为字段值而不是字符串。

aggregate( { $match:{type:”X”}}, { $group:{set_id:”$o_id”, total: {$sum: “$value”}}}, function(err, results){});

运算符$match执行完毕后,将交给$group进行处理的文档如下:

{o_id:”A”, value:50, type:”X”} {o_id:”A”, value:75, type:”X”} {o_id:”B”, value:80, type:”X”}

运算符$group执行完毕后,将把一个对象数组传递给回调函数,该数组中的对象包含字段set_id和total:

{set_id:”A”, total:”125”} {set_id:”B”, total:”80”}

表9.1列出了可在方法aggregate()的operator参数中指定的命令。

表9.1  可在方法aggregate()中使用的聚合运算符

运算符

描述

$project

通过重命名、添加或删除字段来重新定义文档。您还可以重新计算值以及添加子文档。例如,下面的示例包含字段title并排除字段name:{$project:{title:1, name:0}};下面的示例将字段name重命名为title:{$project:{title:”$name”}};下面的示例添加新字段total并根据字段price和tax计算其值:{$project:{total:{$add:[“$price”, “$tax”] } }

$match

使用本书前面讨论的查询运算符过滤文档集,如{$match:{value:{$gt:50}}}

$limit

限制传递给聚合流水线中下一个阶段的文档数,如{$limit:5}

$skip

指定执行聚合流水线的下一个阶段前跳过多少个文档,如{$skip:10}

$unwind

$unwind的值必须是数组字段的名称(必须在该数组字段名前加上$,这样它才会被视为字段名,而不是字符串)。$unwind对指定的数组进行分拆,为其中的每个值创建一个文档,如{$unwind:”$myArr”}

$group

将文档分组并生成一组新文档,供流水线的下一个阶段使用。在$group中必须定义新文档的字段;还可对各组的文档应用分组表达式运算符,如将value字段的值相加:{$group:{set_id:”$o_id”, total: {$sum: “$value”}}}

$sort

将文档交给聚合流水线的下一个阶段前,对它们进行排序。$sort指定包含属性field:的对象,其中为1(升序)或-1(降序),如{$sort: {name:1, age:-1}}

9.2.3 使用聚合表达式运算符 使用聚合运算符时,将创建新文档,这些文档将传递给聚合流水线的下一个阶段。MongoDB聚合框架提供了很多表达式运算符,可用于计算新字段的值以及对文档中的既有字段进行比较。

使用聚合运算符$group时,新文档定义的字段将对应于多个原始文档。MongoDB提供了一组运算符,可用于对原始文档的字段执行计算,以得到新文档的字段值。例如,设置新文档的字段maximumt,使其表示相应分组中原始文档的最大value字段值。表9.2列出了可在聚合运算符$group中使用的表达式运算符。

表9.2  可在聚合运算符$group中使用的表达式运算符

运算符

描述

$addToSet

返回一个数组,包含当前文档组中指定字段的不同值,如colors: {$addToSet: “$color”}

$first

返回当前文档组中第一个文档的指定字段的值,如firstValue:{$first: “$value”}

$last

返回当前文档组中最后一个文档的指定字段的值,如lastValue:{$last: “$value”}

$max

返回当前文档组中指定字段的最大值,如maxValue:{$max: “$value”}

$min

返回当前文档组中指定字段的最小值,如minValue:{$min: “$value”}

$avg

返回当前文档组中指定字段的平均值,如aveValue:{$avg: “$value”}

$push

返回一个数组,包含当前文档组中每个文档的指定字段值,如username:{$push: “$username”}

$sum

返回当前文档组中各个文档的指定字段值之和,如total:{$sum: “$value”}

计算新字段的值时,还可使用多个字符串运算符和算术运算符。表9.3列出了在聚合运算符中计算新字段的值时,较为常用的一些表达式运算符。

表9.3  在聚合表达式中使用的字符串运算符和算术运算符

运算符

描述

$add

将一系列数字相加,如valuePlus5:{$add:[“$value”, 5] }

$divide

接受两个数字,并将第一个数字与第二个数字相除,如valueDividedBy5:{$divide:[“$value”, 5] }

$mod

接受两个数字,并计算第一个数字除以第二个数字的余数,如valueMod5:{$mod:[“$value”, 5] }

$multiply

计算一系列数字的乘积,如valueTimes5:{$multiply:[“$value”, 5] }

$subtract

接受两个数字,并将第一个数字与第二个数字相减,如valueMinus5:{$subtract:[“$value”, 5] }

$concat

拼接多个字符串,如title:{$concat:[“$title”, “ “, “$name”] }

$strcasecmp

比较两个字符串,并返回一个指出比较结果的整数,如isTest:{$strcasecmp:[“$value”, “test”] }

$substr

返回字符串的指定部分,如hasTest:{$substr:[“$value”, “test”] }

$toLower

将字符串转换为小写,如titleLower:{$toLower:”$title”}

$toUpper

将字符串转换为大写,如titleUpper:{$toUpper:”$title”}

在MongoDB shell中使用映射-归并生成新的数据结果 MongoDB的一大优点是能够对数据查询结果进行映射-归并,生成与原始集合截然不同的结构。映射-归并指的是对数据库查询结果进行映射,再归并为截然不同的格式以方便使用。

通过使用映射-归并,可在MongoDB服务器上操作数据,再将结果返回给客户端,从而降低客户端的处理负担及需要返回的数据量。接下来的几小节介绍MongoDB映射-归并框架及其用法。

9.3.1 理解方法mapReduce() Collection对象提供了方法mapReduce(),可用于对数据执行映射-归并操作,再将结果返回给客户端。方法mapReduce()的语法如下:

mapReduce(map, reduce, arguments)

参数map是一个函数,将对数据集中的每个对象执行它来生成一个键和值,这些值被加入到与键相关联的数组中,供归并阶段使用。map函数的格式如下:

function(){

emit(key, value); } 参数reduce是一个函数,将对map函数生成的每个对象执行它。reduce函数必须将键作为第一个参数,将与键相关联的值数组作为第二个参数,并使用值数组来计算得到与键相关联的单个值,再返回结果。reduce函数的格式如下: function(key, values){ return result; } 方法mapReduce()的参数arguments是一个对象,指定了检索传递给map函数的文档时使用的选项。表9.4列出了可在参数arguments中指定的选项。 在下面的映射-归并示例中,指定了选项out和query: results = myCollection.mapReduce( function() { emit(this.key, this.value); }, function(key, values){ return Array.sum(values); }, { out: {inline: 1}, query: {value: {$gt: 6} } ); 表9.4  在方法mapReduce()的参数arguments中可设置的选项 选项 描述 out 这是唯一一个必不可少的选项,它指定将映射-归并操作结果输出到什么地方,可能取值如下。 ● 内嵌: 在内存中执行操作,并在对客户端的响应中返回归并对象,如out: {inline:1}   ● 输出到集合: 指定将结果插入到一个集合中,如果指定的集合不存在,就创建它,如out: outputCollection   ● 输出到集合并指定要采取的措施: 指定要将结果输出到哪个数据库的哪个集合,并指定措施replace、merge或reduce,如out: {replace: outputCollection} Query 指定查询运算符,用于限制将传递给map函数的文档 sort 指定排序字段,用于在将文档传递给map函数前对其进行排序。指定的排序字段必须包含在集合的既有索引中 limit 指定最多从集合中返回多少个文档 finalize 在reduce函数执行完毕后执行,finalize函数必须将键和文档作为参数,并返回修改后的文档,如: finalize: function(key, document){ <do stuff to modify document> return document; } scope 指定可在map、reduce和finalize函数中访问的全局变量 jsMode 布尔值,为true时,不将从map函数返回的表示中间数据集的JavaScript对象转换为BSON。默认为false verbose 布尔值,为true时,发送给客户端的结果中将包含时间(timing)信息。默认为true -------------------------- MongoDB 查询文档使用 find() 方法。 find() 方法以非结构化的方式来显示所有文档。 ## MongoDB查询文档 MongoDB 查询数据的语法格式如下: ```text db.collection.find(query, projection) ``` - query :可选,使用查询操作符指定查询条件 - projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。 如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下: ```text db.col.find().pretty() ``` pretty() 方法以格式化的方式来显示所有文档。 ## MongoDB AND 条件 MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。 语法格式如下: ```text db.col.find({key1:value1, key2:value2}).pretty() ``` ## MongoDB OR 条件 MongoDB OR 条件语句使用了关键字 $or,语法格式如下: ```text db.col.find( { $or: [ {key1: value1}, {key2:value2} ] } ).pretty() ``` ## 条件操作符 条件操作符用于比较两个表达式并从mongoDB集合中获取数据 MongoDB中条件操作符有: - (>) 大于 - $gt - (<) 小于 - $lt - (>=) 大于等于 - $gte - (<= ) 小于等于 - $lte ### MongoDB (>) 大于操作符 - $gt 如果你想获取 "col" 集合中 "likes" 大于 100 的数据,你可以使用以下命令: ```text db.col.find({likes : {$gt : 100}}) ``` 类似于SQL语句: ```text Select * from col where likes > 100; ``` ### MongoDB(>=)大于等于操作符 - $gte 如果你想获取"col"集合中 "likes" 大于等于 100 的数据,你可以使用以下命令: ```text db.col.find({likes : {$gte : 100}}) ``` 类似于SQL语句: ```text Select * from col where likes >=100; ``` ### MongoDB (<) 小于操作符 - $lt ```text db.col.find({likes : {$lt : 150}}) ``` 类似于SQL语句: ```text Select * from col where likes < 150; ``` ### MongoDB (<=) 小于等于操作符 - $lte 如果你想获取"col"集合中 "likes" 小于等于 150 的数据,你可以使用以下命令: ```text db.col.find({likes : {$lte : 150}}) ``` 类似于SQL语句: ```text Select * from col where likes <= 150; ``` ### MongoDB 使用 (<) 和 (>) 查询 - $lt 和 $gt 如果你想获取"col"集合中 "likes" 大于100,小于 200 的数据,你可以使用以下命令: ```text db.col.find({likes : {$lt :200, $gt : 100}}) ``` 类似于SQL语句: ```text Select * from col where likes>100 AND likes<200; ``` ## $type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。 如果想获取 "col" 集合中 title 为 String 的数据,你可以使用以下命令: ```text db.col.find({"title" : {$type : 2}}) 或 db.col.find({"title" : {$type : 'string'}}) ``` ## Limit() 方法 如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。 limit()方法基本语法如下所示: ```text >db.COLLECTION_NAME.find().limit(NUMBER) ``` 注:如果你们没有指定limit()方法中的参数则显示集合中的所有数据。 ## Skip() 方法 我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。 skip() 方法脚本语法格式如下: ```text db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER) ``` 注:skip()方法默认参数为 0 。 ## sort() 方法 在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。 sort()方法基本语法如下所示: ```text db.COLLECTION_NAME.find().sort({KEY:1}) ``` ## 聚合 MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。 有点类似 SQL 语句中的 count(*)。 ## aggregate() 方法 MongoDB中聚合的方法使用aggregate()。 aggregate() 方法的基本语法格式如下所示: ```text db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) ``` 下表展示了一些聚合的表达式: | 表达式 | 描述 | 实例 | |-----------|-------------------------------------------|---------------------------------------------------------------------------------------| | $sum | 计算总和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) | | $avg | 计算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) | | $min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) | | $max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) | | $push | 将值加入一个数组中,不会判断是否有重复的值。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) | | $addToSet | 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) | | $first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) | | $last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) | ## 管道的概念 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。 MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。 表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。 这里我们介绍一下聚合框架中常用的几个操作: - $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。 - $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。 - $limit:用来限制MongoDB聚合管道返回的文档数。 - $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。 - $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。 - $group:将集合中的文档分组,可用于统计结果。 - $sort:将输入文档排序后输出。 - $geoNear:输出接近某一地理位置的有序文档。 ### 管道操作符实例 - $project实例 ```text db.article.aggregate( { $project : { title : 1 , author : 1 , }} ); ``` 这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样: ```text db.article.aggregate( { $project : { _id : 0 , title : 1 , author : 1 }}); ``` - $match实例 ```text db.articles.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] ); ``` $match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。 - $skip实例 ```text db.article.aggregate( { $skip : 5 }); ``` 经过$skip管道操作符处理后,前五个文档被"过滤"掉。 ## 查询分析 MongoDB 查询分析可以确保我们所建立的索引是否有效,是查询语句性能分析的重要工具。 MongoDB 查询分析常用函数有:explain() 和 hint()。 ### 使用 explain() explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。 接下来我们在 users 集合中创建 gender 和 user_name 的索引: ```text db.users.ensureIndex({gender:1,user_name:1}) ``` 现在在查询语句中使用 explain : ```text db.users.find({gender:"M"},{user_name:1,_id:0}).explain() ``` 以上的 explain() 查询返回如下结果: ```text { "cursor" : "BtreeCursor gender_1_user_name_1", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 0, "nscanned" : 1, "nscannedObjectsAllPlans" : 0, "nscannedAllPlans" : 1, "scanAndOrder" : false, "indexOnly" : true, "nYields" : 0, "nChunkSkips" : 0, "millis" : 0, "indexBounds" : { "gender" : [ [ "M", "M" ] ], "user_name" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ] } } ``` 现在,我们看看这个结果集的字段: - indexOnly: 字段为 true ,表示我们使用了索引。 - cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。 - n:当前查询返回的文档数量。 - nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。 - millis:当前查询所需时间,毫秒数。 - indexBounds:当前查询具体使用的索引。 ### 使用 hint() 虽然MongoDB查询优化器一般工作的很不错,但是也可以使用 hint 来强制 MongoDB 使用一个指定的索引。 这种方法某些情形下会提升性能。 一个有索引的 collection 并且执行一个多字段的查询(一些字段已经索引了)。 如下查询实例指定了使用 gender 和 user_name 索引字段来查询: ```text >db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}) ``` 可以使用 explain() 函数来分析以上查询: ```text >db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain() ```

Search

    微信好友

    博士的沙漏

    Table of Contents