『count』
count是最简单的聚合工具,返回集合中的文档数量:
> db.foo.count()
0
> db.foo.insert({"x" : 1})
> db.foo.count()
1
也可以传递查询,Mongo则会计算查询结果的数量:
> db.foo.insert({"x" : 2})
> db.foo.count()
2
> db.foo.count({"x" : 1})
1
『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}
『group』
group先选定分组所依据的键,而后MongoDB就会将集合依据选定键值的不同分成若干组。然后通过聚合每一组内的文档,产生一个结果文档。
(这个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}
想获得的结果就是每天最后的价格列表,就像这样:
[
{"day" : "2010/10/03", "time" : "10/3/2010 05:00:23 GMT-400", "price" : 4.10}
{"day" : "2010/10/04", "time" : "10/4/2010 11:28:39 GMT-400", "price" : 4.27}
{"day" : "2010/10/06", "time" : "10/6/2010 05:27:58 GMT-400", "price" : 4.30}
]
先把集合按照天分组,然后在每一组里取包含最新时间戳的文档,将其放置到结果中就完成了。整个过程:

> db.runCommnad({"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;
            }
        }}})

分解步骤如下:
"ns" : "stocks"
指定要进行分组的集合。
"key" : "day"
指定文档分组依据的键。这里就是"day"键,所有"day"值相同的文档被划分到一组。
"initial" : {"time" : 0}
每一组reduce函数调用的初试时间,会作为初始文档传递给后续过程。每一组的所有成员都会使用这个累加器,所以改变会保留住。
"reduce" : function(doc, prev) { ... }
每个文档都对应一次这个调用。系统会传递两个参数:当前文档和累加器文档(本组当前的结果)。本例中,想让reduce函数比较当前文档的时间和累加器文档的时间。如果当前文档的时间更近,则将累加器的日期和价格替换成当前文档的值。每一组都有一个独立的累加器,所以不用担心不同的日期使用同一个累加器。
在问题一开始的描述中,就提到只要最近30天的股价。然而,这里迭代了整个集合,这就是为什么要添加"condition",因为这样就可以值处理满足条件的文档了。

> db.runCommnad({"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;
            }},
        "condition" : {"day" : {"$gt" : "2010/09/30"}}
        }})    

最后就会返回由30个文档组成的数组,每个组一个文档。魅族还有分组依据的键(这里就是"day" : string)以及这组最终的prev值。如果有的文档没有依据的键,就都会被分到一组,相应的部分就会使用"day : null"这样的形式。在"condition"中加入"day" : {"$exists" : true}就可以去掉这组。
使用完成器
完成器(finalizer)用以精简从数据库传到用户的数据。
例:博客,其中每篇文章都有多个标签(tag)。现在要找出每天最热点的标签。可以(再一次)按天分组,为每一个标签计数:

> db.posts.group({
    "key" : {"tags" : true},
    "initial" : {"tags" : {}},
    "$reduce" : function(doc, prev) {
        for (i in doc.tags) {
            if (doc.tags[i] in prev.tags) {
                prev.tags[doc.tags[i]]++;
            } else {
                prev.tags[doc.tags[i]] = 1;
            }
        }
    }})

结果会是这样:
[
{"day" : "2010/01/12", "tags" : {"nosql" : 4, "winter" : 10, "sledding" : 2}}
{"day" : "2010/01/13", "tags" : {"soda" : 5, "php" : 2}}
{"day" : "2010/01/14", "tags" : {"python" : 6, "winter" : 4, "nosql" : 15}}
]
然后,使用finalizer过滤服务器传递给客户端过程中不需要的数据:

> db.posts.group({
    "key" : {"tags" : true},
    "initial" : {"tags" : {}},
    "$reduce" : function(doc, prev) {
        for (i in doc.tags) {
            if (doc.tags[i] in prev.tags) {
                prev.tags[doc.tags[i]]++;
            } else {
                prev.tags[doc.tags[i]] = 1;
            }
        },
    "finalize" : function(prev) {
        var mostPopular = 0;
        for(i in prev.tags) {
            if(prev.tags[i] > mostPopular) {
                prev.tag = i;
                mostPopular = prev.tags[i]
            }
        }
        delete prev.tags
    }}})

然后,服务器会返回希望的结果:
[
{"day" : "2010/01/12", "tag" : "winter"},
{"day" : "2010/01/13", "tag" : "soda"},
{"day" : "2010/01/14", "tag" : "nosql"}
]
finalize嫩刚修改传递的参数也能返回新值。
将函数作为键使用
定义分组函数要用到"$keyf"键。
例如,由于有很多作者,给文章分类时可能不规律的用了大小写,为了让"MongoDB"和"mongodb"分在同一个组,需要使用"$keyf"定义一个函数来确定文档分组所依据的键:
> db.posts.group({"ns" : "posts", "$keyf" : function(x) { return x.category.toLowerCase(); }, "initializer" : ... })
有了"$keyf"就能依据各种复杂的条件进行分组了。
『MapReduce』
MapReduce:
①映射(map):将操作映射到集合中的每个文档。
②洗牌(shuffle):按照键分组,并将产生的键值组成列表放到对应的键中。
③化简(reduce):把列表中的值简化成一个单值。这个值被返回,然后接着进行洗牌,直到每个键的列表只有一个值为止,这个值就是最后结果。
使用MapReduce的代价就是速度:group不是很快,MapReduce更慢,绝对不要用在“实时”环境中。
【MapReduce例1:找出集合中的所有键】
MongoDB没有模式,所以并不知晓每个文档有多少个键。通常找到集合的所有键的最好方式就是用MapReduce。在本例中,还会记录每个键出现了多少次。
在映射(map)环节,想得到文档中的每个键。map函数使用emit“返回”要处理的值。emit会给MapReduce一个键(类似于前面group多使用的键)和一个值。这里用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, emit) {
    total = 0;
    for (var i in emits) {
        total += emits[i].count;
    }
    return {"count" : total};
    }

reduce一定要能被反复调用,不论是映射(map)环节还是前一个简化(reduce)环节。所以reduce返回的文档必须能作为reduce的第二个参数的一个元素。
reduce能处理emit文档和其他reduce结果的各种组合。
MapReduce函数类似这样:

> mr = db.runCommand({"mapreduce" : "foo", "map" : map, "reduce" : reduce})
{
    "result" : "tmp.mapreduce_1266787811_1",
    "timeMillis" : 12,
    "counts" : {
        "input" : 6,
        "emit" : 14,
        "output" : 5
    },
    "ok" : true
}

MapReduce返回的文档包含很多与操作有关的元信息:
·"result" : "tmp.mapreduce_1266787811_1"
这是存放MapReduce结果的集合名。这是一个临时集合,MapReduce的连接关闭后自动就被删除了。
·"timeMillis" : 12
操作花费的时间,单位是毫秒。
·"count" : { ... }
这个内嵌文档包含3个键。
·"input" : 6
发送到map函数的文档个数。
·"emit" : 14
在map函数中emit被调用的次数。
·"output" : 5
结果集合中创建的文档数量。
"count"对调试非常有帮助。
对结果几核进行查询会发现原有集合的所有键及其计数:
> db[mr.result].find()
{ "_id" : "_id", "value" : {"count" : 6} }
{ "_id" : "a", "value" : { "count" : 4 } }
{ "_id" : "b", "value" : { "count" : 2 } }
{ "_id" : "x", "value" : { "count" : 1 } }
{ "_id" : "y", "value" : { "count" : 1 } }
每个键值变为一个"_id",最终花间步骤的结果变为"value"。
【MapReduce例2:网页分类】
假设有一个网站,人们可以提交其他网页的链接,比如rebbit.com,提交者可以给这个链接做标签,表明主题,比如"politics","geek"或者"icanhascheezburger",可以用MapReduce找出哪个主题最为热门,热门与否由最近的投票决定。
首先,建立一个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" : score});
    }
};

现在就简化同一个标签的所有值,形成这个标签的分数:

reduce = function(key, emits) {
    vat 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;
}

最终的集合包含每个标签的URL列表和表示该标签流行程度的分数。
-- MapReduce部分没有完全掌握! --
『MongoDB和MapReduce』
前面两个例子只用到了mapreduce、map和reduce键。这三个键是必须的,除此之外MapReduce命令还有很多可选的键。
·"finalize":函数
将reduce的结果发送给这个键,这是处理过程的最后一步。
·"keeptemp":布尔
连接关闭时临时结果集合是否保存。
·"output":字符串
集合结果的名字。设定该项则隐含着keeptemp : true。
·"query":文档
会在发往map函数前,先用指定条件过滤文档。
·"sort":文档
在发往map前先给文档排序(与limit一同使用非常有用)。
·"limit":整数
发往map函数的文档数量的上限。
·"scope":文档
JavaScript代码中要用到的变量。
·"verbose":布尔
是否产生更加详尽的服务器日志。
⒈finalize函数
finalize会在最后reduce得到输出后执行,然后将结果存到临时集合中。
⒉保留结果集合
设置keeptemp为true或者设置out选项给集合取个好点的名字。
⒊对文档子集执行MapReduce
有时候需要对集合的一部分执行MapReduce。只需要在传给map函数前添加一个查询来过滤一下文档就好了。
过滤主要就是用"query"、"limit"和"sort"键指定。
"query"键的值是一个查询文档。通常查询返回的结果就传递给了map函数。例如,有个应用程序做跟踪分析,需要上周的概要,只要使用如下命令对上周的文档执行MapReduce就好了:
> db.runCommand({"mapreduce" : "analytics", "map" : map, "reduce" : reduce, "query" : {"date" : {"$gt" : week_ago}}})
sort选项一般和limit一铜发挥重要作用。limit也可以单独使用,用来截取一部分文档发送给map函数。
如果在上个例子中想分析最近10000个页面视图(而不是最近一周的),则可以借助limit和sort:
> db.runCommand({"mapreduce" : "analytics", "map" : map, "reduce" : reduce, "limit" : 10000, "sort" : {"date" : -1}})
query、limit、sort可以随意组合,但要是没有limit,sort单独使用的用处不大。
⒋使用作用域
例:在之前的一个例子中,用1/(new Date() - this.date)计算了页面的新近程度。还可以将当前的日期作为作用域的一部分传递进去:
> db.runCommand({"mapreduce" : "webpages", "map" : map, "reduce" : reduce, "scope" : {now : new Date()}})
这样,在map函数中就能计算1/(now-this.date)了。
⒌获得更多的输出
如果想看看MapReduce的运行过程,可以用"verbose" : true。
也可以用print把map、reduce、finalize过程中的信息输出到服务器日志上。

MongoDB学习笔记五:聚合的更多相关文章

  1. Mongodb学习笔记五(C#操作mongodb)

    mongodb c# driver(驱动)介绍 目前基于C#的mongodb驱动有两种,分别是官方驱动(下载地址)和samus驱动(下载地址). 本次我们只演示官方驱动的使用方法. 官方驱动文档查看 ...

  2. MongoDB学习笔记-05 聚合

    MongoDB除了基本查询功能之外,还有强大的聚合工具,其中包括:count().distinct().group().mapreduce. 计数函数count count是最简单的聚合工具,用于返回 ...

  3. MongoDB学习笔记五—查询上

    数据准备 { , "goods_name" : "KD876", "createTime" : ISODate("2016-12- ...

  4. MongoDB学习笔记(五) MongoDB文件存取操作

    由于MongoDB的文档结构为BJSON格式(BJSON全称:Binary JSON),而BJSON格式本身就支持保存二进制格式的数据,因此可以把文件的二进制格式的数据直接保存到MongoDB的文档结 ...

  5. MongoDB学习笔记(转)

    MongoDB学习笔记(一) MongoDB介绍及安装MongoDB学习笔记(二) 通过samus驱动实现基本数据操作MongoDB学习笔记(三) 在MVC模式下通过Jqgrid表格操作MongoDB ...

  6. mongoDB 学习笔记纯干货(mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)

    最后更新时间:2017-07-13 11:10:49 原始文章链接:http://www.lovebxm.com/2017/07/13/mongodb_primer/ MongoDB - 简介 官网: ...

  7. 【转】mongoDB 学习笔记纯干货(mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)

    mongoDB 学习笔记纯干货(mongoose.增删改查.聚合.索引.连接.备份与恢复.监控等等) http://www.cnblogs.com/bxm0927/p/7159556.html

  8. MongoDB学习笔记(五)--复制集 && sharding分片

    主从复制                                                                                       主从节点开启 主节 ...

  9. MongoDB 学习笔记(原创)

    MongoDB 学习笔记 mongodb 数据库 nosql 一.数据库的基本概念及操作 SQL术语/概念 MongoDB术语/概念 解释/说明 database database 数据库 table ...

随机推荐

  1. 一个简单的游戏开发框架(五.对象Object)

    前面提到我们把行为Action从对象Object中分离了出来,用各种不同的行为组合出对象的功能.大家都知道,面向对象的一个类,就是数据和操作的集合.操作(行为)被分离出来了,数据怎么办呢?操作依赖的数 ...

  2. 在iframe中获取本iframe DOM引用

    window.frameElement 获取本iframe DOM window.frameElement.contentDocument.getElementById('id') 获取这个ifram ...

  3. [转]Ubuntu中root用户和user用户的相互切换

    [转]Ubuntu中root用户和user用户的相互切换 http://www.cnblogs.com/weiweiqiao99/archive/2010/11/10/1873761.html Ubu ...

  4. android 性能优化大纲

    性能优化系列 分为三个部分:视图篇 逻辑篇  和代码规范篇 . ------2016/9/6  视图篇      主要涵盖视图树层级优化.自定义视图.图片优化,常用布局性能缺陷等多个方面 .把平常经常 ...

  5. Jenkins 快速搭建持续集成环境

    持续集成概述 什么是持续集成 随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题.尤其是近些年来,敏捷(Agile) 在软件工程领域 ...

  6. SQL判断一个数是整数还是小数

    DECLARE @number1 AS numeric(10,2),@number2 AS numeric(10,2) SELECT @number1=10.00,@number2=10.2 SELE ...

  7. 快速切换天财商龙门店后台.VB6.0

    名称:快速切换天财商龙门店后台 作者:landv 时间:2015年8月21日 功能:快速切换天财商龙门店后台 环境:VB6.0 注:懒的一行一行修改文件了,直接覆盖配置文件,Tcgem.ini 只能反 ...

  8. 简洁灵活的前端框架------BootStrap

      前  言 Bootstrap,来自 Twitter,是目前很受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷.[1] ...

  9. angular2 学习笔记 ( angular cli & npm version manage npm 版本管理 )

    更新 : 2017-05-05 现在流行 Yarn ! 它是 facebook google 推出的东西. 算是补助 npm 做的不够好的地方. 源码依然是发布去 npm,只是下载接口换掉罢了哦. n ...

  10. 迭代和JDB调试

    迭代和JDB调试 题目要求 1 使用C(n,m)=C(n-1,m-1)+C(n-1,m)公式进行递归编程实现求组合数C(m,n)的功能 2 m,n 要通过命令行传入 3 提交测试运行截图(至少三张:正 ...