mongodb进阶二之mongodb聚合

上篇我们说了mongodb的高级查询:http://blog.csdn.net/stronglyh/article/details/46817789

这篇说说mongodb的聚合

一:mongodb中有很多聚合框架,从而实现文档的变换和组合,主要有一下构件

构件类别                操作符

筛选(filtering)         $match

投射(projecting)     $project

分组(grouping        $group

排序(sorting)          $sort

限制(limiting)         $limit

跳过(skipping)       $skip

如果需要聚合数据,那么要使用aggregate方法

db.collection.aggregate(聚合条件);

单个操作,传入一个json对象作为集合条件,如

db.users.aggregate({

$project:{

_id:0,

name:1,

}

})

如果需要多个操作符,传入一个数组作为条件,如

db.users.aggregate([

{ $skip : 5 },

{ $project:{ _id:0, name:1, } }

])

1.1:$match匹配

$match用于对文档集合进行筛选,之后就可以在筛选得到的文档子集上做聚合。

例如,如果想对北京(简写BJ)的用户做统计,就可以使用{$match:{"area":"BJ"}}。"$match"可以使用所有常规的查询操作符("$gt"、"$lt"、"$in"等)。有一个里外需要注意:不能在"$match"中使用地理空间操作符。

通常,在实际使用中应该尽可能将"$match"放在管道的前面位置。这样做有两个好处:

一是可以快速将不需要的文档过滤掉,以减少管道的工作量;

二是如果在投射和分组之前执行"$match",查询可以使用索引。

1.2:$project投射

相对于“普通”的查询而言,管道中的投射操作更加强大。使用"$project"可以从子文档中提取字段,可以重命名字段,还可以在这些字段上进行一些有意思的操作。

最简单的一个"$project"操作是从文档中选择想要的字段。可以指定包含或者不包含一个字段,它的语法和查询中的第二个参数类似。如果在原来的集合上执行下面的代码,返回的结果文档中只包含一个"author"字段。

db.articles.aggregate({"$project":{"author":1,"_id":0})

默认情况下,如果文档中存在"_id"字段,这个字段就会被返回。

赶快亲自动手敲下,看看运行结果。

也可以将投射过的字段进行重命名。例如,可以将每个用户文档的"_id"在返回结果中重命名为"userId":

db.articles.aggregate({"$project":{"userId":"$_id","_id":0}});

这里的"$fieldname"语法是为了在聚合框架中引用fieldname字段(上面的例子中是"_id")的值。例如,"$age"会被替换为"age"字段的内容(可能是数值,可能是字符串),"$tag.3"会被替换为tags数组中的第4个元素。所以,上面例子中的"$_id"会被替换为进入管道的每个文档的"_id"字段的值。

注意,必须明确指定将"_id"排除,否则这个字段的值会被返回两次:一次标记为"userId",一次被标记为"_id"。可以使用这种技术生成字段的多个副本,以便在之后"$group"中使用。

继续学习

1.3:$group分组

$group操作可以将文档依据特定字段的不同值进行分组。举例:

如果有一个学生集合,希望按照分数等级将学生分为多个组,可以根据"grade"字段进行分组。

如果选定了需要进行分组的字段,就可以将选定的字段传递给"$group"函数的"_id"字段。对于上面的例子,相应代码如下:

{"$group":{"_id":"$grade"}}

例如,学生分数等级进行分组的结果可能是:

{"result":[{"_id":"A+"},{"_id":"A"},{"_id":"A-"},...,{"_id":"F"}],"ok":1}

分组操作符

这些分组操作符允许对每个分组进行计算,得到相应的结果。

1.4:$unwind拆分

拆分(unwind)可以将数组中的每一个值拆分为单独的文档。

例如,如果有一篇拥有多条评论的博客文章,可以使用$unwind将每条评论拆分为一个独立的文档:

db.blog.findOne()

{

"_id":ObjectId("5359f6f6ec7452081a7873d7"),

"author":"Tom",

"conments":[

{

"author":"Mark",

"date":ISODate("2014-01-01T17:52:04.148Z"),

"text":"Nice post"

},

{

"author":"Bill",

"date":ISODate("2014-01-01T17:52:04.148Z"),

"text":"I agree"

}

]

}

db.blog.aggregate({"$unwind":"$comments"})

{

"results":

{

"_id":ObjectId("5359f6f6ec7452081a7873d7"),

"author":"Tom",

"comments":{

"author":"Mark",

"date":ISODate("2014-01-01T17:52:04.148Z"),

"text":"Nice post"

}

},

{

"_id":ObjectId("5359f6f6ec7452081a7873d7"),

"author":"Tom",

"comments":{

"author":"Bill",

"date":ISODate("2014-01-01T17:52:04.148Z"),

"text":"I agree"

}

}

}

如果希望在查询中得到特定的子文档,这个操作符就会非常有用:先使用"$unwind"得到所有子文档,再使用"$match"得到想要的文档。例如,如果要得到特定用户的所有评论(只需要得到评论,不需要返回评论所属的文章),使用普通的查询是不可能做到的。但是,通过提取、拆分、匹配、就很容易了:

db.blog.aggregate({"$project":{"coomments":"$comments"}},

{"$unwind":"$comments"},

{"$match":{"comments.author":"Mark"}})

由于最后得到的结果仍然是一个"comments"子文档,所以你可能希望再做一次投射,以便让输出结果更优雅。

1.5:sort排序

可以根据任何字段(或者多个字段)进行排序,与普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一阶段进行排序,这时的排序操作可以使用索引。否则,排序过程就会比较慢,而且会占用大量内存。

可以在排序中使用文档中实际存在的字段,也可以使用在投射时重命名的字段:

db.employees.aggregate(

{

"$project":{

"compensation":{

"$add":["$salary","$bonus"]

},

name:1

}

},

{

"$sort":{"compensation":-1,"name":1}

}

)

这个例子会对员工排序,最终的结果是按照报酬从高到低,姓名从A到Z的顺序排序。

排序的方向可以是1(升序)和-1(降序)。

与前面讲过的"$group"一样,"$sort"也是一个无法使用流式工作方式的操作符。"$sort"也必须要接收到所有文档之后才能进行排序。在分片环境下,先在各个分片上进行排序,然后将各个分片的排序结果发送到mongos做进一步处理。

1.6:$limit会接受一个数字n,返回结果集中的前n个文档。

$skip也是接受一个数字n,丢弃结果集中的前n个文档,将剩余文档作为结果返回。在"普通"查询中,如果需要跳过大量的数据,那么这个操作符的效率会很低。在聚合中也是如此,因为它必须要先匹配到所有需要跳过的文档,然后再将这些文档丢弃。

1.7:使用管道

应该尽量在管道的开始阶段(执行"$project"、"$group"或者"$unwind"操作之前)就将尽可能多的文档和字段过滤掉。管道如果不是直接从原先的集合中使用数据,那就无法在筛选和排序中使用索引。如果可能,聚合管道会尝试对操作进行排序,以便能够有效使用索引。

二:聚合命令

2.1:count

count是最简单的聚合工具,用于返回集合中的文档数量:

db.users.count()

0

db.users.insert({"x":1})

db.users.count()

1

不论集合有多大,count都会很快返回总的文档数量。

也可以给count传递一个查询文档,Mongo会计算查询结果的数量:

db.users.insert({"x":2})

db.users.count()

2

db.users.count({"x":1})

1

对于分页显示来说总数非常必要:“共439个,目前显示0~10个”。但是,增加查询条件会使count变慢。count可以使用索引,但是索引并没有足够的元数据提供count使用,所以不如直接使用查询来得快。

2.2:distinct

distinct用来找出给定键的所有不同值。使用时必须指定集合和键。

db.runCommand({"distinct":"people","key":"age"})

假设集合中有如下文档

{name:"Ada",age:20}

{name:"Fred",age:35}

{name:"Susan",age:60}

{name:"Andy",age:35}

如果对"age"键使用distinct,会得到所有不同的年龄:

db.runCommand({"distinct":"people","key":"age"})

{"values":[20,35,60],"ok":1}

2.3:group

使用group可以执行更复杂的聚合。先选定分组所依据的键,而后MongoDB就会将集合依据选定键的不同值分成若干组。然后可以对每一个分组内的文档进行聚合,得到一个结果文档。

如果你熟悉SQL,那么这个group和SQL中的GROUP BY 差不多。

假设现在有个跟踪股票价格的站点。从上午10点到下午4点每隔几分钟就会更新某只股票的价格,并保存在MongoDB中。现在报表程序要获得近30天的收盘价。用group就可以轻松办到。

股票集合中包含数以千计如下形式的文档:

{"day" : "2010/10/03","time" : "10/3/2010 03:57:01 GMT-400","price" : 4.23}

{"day" : "2010/10/04","time" : "10/4/2010 11:28:39 GMT-400","price" : 4.27}

{"day" : "2010/10/03","time" : "10/3/2010 05:00:23 GMT-400","price" : 4.10}

{"day" : "2010/10/06","time" : "10/6/2010 05:27:58 GMT-400","price" : 4.30}

{"day" : "2010/10/04","time" : "10/4/2010 08:34:50 GMT-400","price" : 4.01}

我们需要的结果列表中应该包含每天的最后交易时间和价格,就像下面这样:

[

{"time" : "10/3/2010 05:00:23 GMT-400","price" : 4.10}

{"time" : "10/4/2010 11:28:39 GMT-400","price" : 4.27}

{"time" : "10/6/2010 05:27:58 GMT-400","price" : 4.30}

]

先把集合按照"day"字段进行分组,然后在每个分组中查找"time"值最大的文档,将其添加到结果集中就完成了。整个过程如下所示:

> db.runCommand({"group" : {

... "ns" : "stocks",

... "key" : "day",

... "initial" : {"time" : 0},

... "$reduce" : function(doc,prev){

...       if(doc.time > prev.time){

...            prev.price = doc.price;

...            prev.time = doc.time;

...        }

... }}})

三:MapReduce

好烦,说到这,好想跟大伙说说hadoop中的mapreduce,可是最好不要说串掉,要不然就误人子弟了,其实原理都是一样的啦

MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)"和"Reduce(归约)",和它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。它极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。 当前的软件实现是指定一个Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享相同的键组。

3.1:找出集合中的所有键

MongoDB没有模式,所以并不知晓每个文档有多少个键.通常找到集合的所有键的做好方式是用MapReduce。 在映射阶段,想得到文档中的每个键.map函数使用emit 返回要处理的值.emit会给MapReduce一个键和一个值。 这里用emit将文档某个键的记数(count)返回({count:1}).我们为每个键单独记数,所以为文档中的每一个键调用一次emit。 this是当前文档的引用:

> map=function(){

... for(var key in this){

...      emit(key,{count:1})

... }};

这样返回了许许多多的{count:1}文档,每一个都与集合中的一个键相关.这种有一个或多个{count:1}文档组成的数组,会传递给reduce函数.reduce函数有两个参数,一个是key,也就是emit返回的第一个值,另一个参数是数组,由一个或者多个与键对应的{count:1}文档组成。

> reduce=function(key,emits){

... total=0;

... for(var i in emits){

...     total+=emits[i].count;

... }

... return {count:total};

... }

reduce要能被反复被调用,不论是映射环节还是前一个化简环节。reduce返回的文档必须能作为reduce的第二个参数的一个元素。如x键映射到了3个文档{"count":1,id:1},{"count":1,id:2},{"count":1,id:3} 其中id键用于区别。MongoDB可能这样调用reduce:

>r1=reduce("x",[{"count":1,id:1},{"count":1,id:2}])

{count:2}

>r2=reduce("x",[{"count":1,id:3}])

{count:1}

>reduce("x",[r1,r2])

{count:3}

不能认为第二个参数总是初始文档之一(比如{count:1})或者长度固定。reduce应该能处理emit文档和其他reduce返回结果的各种组合。

总之,MapReduce函数可能会是下面这样:

> mr = db.runCommand({"mapreduce" : "foo", "map" : map,"reduce" : reduce})

{

"reduce" : "tmp.mr.mapreduce_1266787811_1", // 这是存放MapReduce结果集合名,临时集合连接关闭自动删除

"timeMillis" : 12,  // 操作花费的时间,单位毫秒

"count" : {

"input" :  6  //发往到map函数的文档个数

"emit"  : 14  //在map函数中emit被调用的次数

"output" : 5  //结果集合中的文档数量

},

"ok" : true

}

3.2:网页分类

我们有这样一个网站,用户可以在其上提交他们喜爱的链接url,比如汇智网(http://www.hubwiz.com),并且提交者可以为这个url添加一些标签,作为主题,其他用户可以为这条信息打分。我们有一个集合,收集了这些信息,然后我们需要看看哪种主题最为热门,热门程度由最新打分日期和所给分数共同决定。

首先建立一个map函数,发出(emit)标签和一个基于流行度和新旧程度的值。

> map = function(){

...     for(var i in this.tags){

...         var recency = 1/(new Date() - this.date);

...         var score = recency * this.score;

...         emit(this.tags[i], {"urls":[this.url], "score":this.score});

...     }

... };

现在就化简同一个标签的所有值,以得到这个标签的分数:

> reduce = function(key, emits) {

...     var total = {"urls":[], "score":0};

...     for(var i in emits) {

...         emits[i].urls.forEach(function(url) {

...             total.urls.push(url);

...         });

...         total.score += emits[i].score;

...     }

...     return total;

... };

3.2:MongoDB和MapReduce

前面两个例子只用到了MapReduce、map和reduce键。这3个键是必需的,但是MapReduce命令还有很多可选的键。

"finalize" : 函数

将reduce的结果发送给这个键,这是处理过程的最后一步。

"keeplize" : 布尔

如果值为true,那么在连接关闭时会将临时结果集合保存下来,否则不保存。

"output" : 字符串

输出集合的名称,如果设置了这项,系统会自动设置keeptemp : true。

"query" : 文档

在发往map函数前,先用指定条件过滤文档。

"sort" : 文档

在发往map函数前给文档排序(与limit一同使用非常有用)。

"limit" : 整数

在发往map函数的文档数量的上限。

"scope" : 文档

可以再Javascript代码中使用的变量。

"verbose" : 布尔

是否记录详细的服务器日志。

感谢汇智网:http://hubwiz.com/

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-03 07:45:51

mongodb进阶二之mongodb聚合的相关文章

深入浅出MongoDB(二)mongoDB简介

MongoDB介绍 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.他支持的数据结构非常的松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型.MongoDB最大的特点是他支持的查询语言是非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引.它的特点是高性能.易部署.易使用,存储数据非常方便. MongoDB功能特性 1.面向集合存储,易存储对象类型的数

mongoDB(二)mongoDB副本集实战

mongoDB副本集实战 背景 mongoDB单台服务器的特点: - 数据有丢失风险 - 单台服务器无法做高可用 mongoDB副本集的特点: - 高可用架构,预防数据丢失 - 多台副本数据保持同步和一致 - mongodb副本集在有问题的时候自动切换 实战准备 副本集环境配置 生产环境至少三台服务器 机器IP主机名mongo端口配置文件路径角色192.168.56.11centos7-node127017/data/mongodb/27017/mongodb.conf 192.168.56.1

走进MongoDB(二)

本文从以下四个方面对mongodb进行介绍 一.聚合操作(aggregate operation) 二.文本搜索(text search) 三.数据模型 (DATA MODELS) 四.数据库安全(security) 一.聚合操作 组合多个数据记录,对分组数据记录进行多种操作,最终返回一个单一的结果 实现方式:聚合管道.map-reduce.单用途聚合方法 1.聚合管道 聚合管道是基于数据处理管道模型上的.数据记录经过 多个阶段的管道 最终被转换为聚合结果集. 最基本的过滤管道提供了改变数据集输

MongoDB 进阶模式设计

转载: http://www.mongoing.com/mongodb-advanced-pattern-design 12月12日上午,TJ在开源中国的年终盛典会上分享了文档模型设计的进阶技巧,就让我们来回顾一下吧: —————————————————————————————————————————————————————————- 从很久以前,我就开始接触开源产品:从最开始的使用.受益者到后来的贡献者,到现在的热情推广者.现在,我是MongoDB的技术顾问.我的职责是为MongoDB的客户和

MongoDB进阶

MongoDB进阶 1.$type操作符 用途:使用MongoDB时,在需要根据字段的类型来查询数据时,可以使用$type操作符来完成. 语法:db.collection.find({字段:{$type:类型}}) 其中,类型的值可以使用以下列出的 Type Number Alias Notes Double 1 "double" String 2 "string" Object 3 "object" Array 4 "array&qu

学习MongoDB 二:MongoDB加入、删除、改动

一.简单介绍 MongoDB是一个高性能.开源.无模式的文档型数据库,是当前NoSQL数据库产品中最热门的一种.数据被分组存储在数据集中,被称为一个集合(Collenction)和对于存储在MongoDB数据库中的文件,我们不须要知道它的不论什么结构定义的自由模式,在存储数据时是以键-值对的集合键是字符串,值能够是数据类型集合里的随意类型,包含数组和文档. MongoDB存储在集合中的全部文件,集合是一组有一组共享公共索引的相关文档.集合类似于关系数据库中的表.在MongoDB中,这些操作改动单

小白来学MongoDB(二)

一.关于windows上使用mongodb mongodb下载地址 官方文档-在windows下安装mongodb 注意:mongodb不支持windows xp系统 在windows上安装mongodb也很简单,点击上面的链接,下载32位或者64位的zip压缩包.或者msi直接安装,在windows上的安装包比较大,110M以上. 下载下来后,解压文件,或者直接运行msi,其中msi直接安装后,默认安装在了C:\Program Files,找到开头是mongodb的,就是了.下面是本人配置的步

浅尝key-value数据库(二)——MongoDB的优与劣

浅尝key-value数据库(二)——MongoDB的优与劣 MongoDB的名字取自英文单词"humongous"的中间五个字母,是一个C++开发的基于分布式文件存储的数据库开源项目.他的文件存储格式是BSON(Binary JSON),因此可以高效存储二进制数据,例如图像.视频等大对象. 由于我是CentOS x86_64的系统,于是安装MongoDB非常简单: vi /etc/yum.repos.d/mongo.repo [10gen] name=10gen Repository

MongoDB(二)

MongoDB入门教程(包含安装.常用命令.相关概念.使用技巧.常见操作等) http://www.jb51.net/article/51514.htm 这篇文章主要介绍了MongoDB入门教程,包含安装.常用命令.相关概念.使用技巧.常见操作等,是一篇比较好的入门文章,需要的朋友可以参考下 一.安装和配置   MongoDB 的官方下载站是 http://www.mongodb.org/downloads,可以去上面下载最新的安装程序   Windows 平台的安装   ● 步骤一: 下载 M