`
onelark
  • 浏览: 32383 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

MongoDB权威指南(5)- 聚合

 
阅读更多

除了基本的查询功能外,mongoDB还提供了聚合工具,从简单的计数到使用MapReduce进行复杂数据的分析等。

1.count

最简单的聚合工具就是count了,它返回document的数量

> db.foo.count()
0
> db.foo.insert({"x" : 1})
> db.foo.count()
1

也可以传递一个查询条件,计算符合条件的结果个数

> db.foo.insert({"x" : 2})
> db.foo.count()
2
> db.foo.count({"x" : 1})
1


2.distinct

distinct命令返回指定的key的所有不同的值。你必须指定一个collection和一个key。

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

假设我们的collection里的document是这样子的:

{"name" : "Ada""age" : 20}
{
"name" : "Fred""age" : 35}
{
"name" : "Susan""age" : 60}
{
"name" : "Andy""age" : 35}

那么返回的结果就是

> db.runCommand({"distinct" : "people""key" : "age"})
{
"values" : [203560], "ok" : 1}


3.group

group提供了更加复杂的聚合功能,它跟SQL里边的group by很类似,你需要指定一个group by的key,mongoDB按照这个key的值把collection分成不同的组,经过聚合后每个组都产生一个结果document。

假设我们有一个站点用来跟踪股票价格,从上午10点到下午4点,每隔几分钟就会有最新的股票价格存储进数据库,作为报表程序的一部分,我们想找出过去30天的收盘价,使用group就可以很容易做到。

股票价格的collection里有成千上万条纪录,格式如下:

{"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分组,找到每组里时间戳最新的记录,把它放到结果集里

> 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;
...   }
... }}})
  • "ns" : "stocks"
    指定对哪个collection运行group命令
  • "key" : "day"
    指定按那个key进行分组
  • "initial" : {"time" : 0}
    累计器初始值,每个分组第一次调用reduce方法的时候传递给它的值,在一个分组里边,始终使用同一个累计器,对累计器的修改会被保持下来。
  • "$reduce" : function(doc, prev) { ... }
    collection里的每个document,都要对之调用reduce方法,传递两个参数给它,第一个是当前的document,第二个是累计器 document,累计器document就是到目前为止分组内的计算结果。(ps:不知道它为啥起个名字叫prev,使用total啊 accumulation之类的不是更容易理解些,我一眼看上去还以为是前一个document。)我们这个例子里,使用reduce方法来比较当前的 document和累计器document的时间,如果当前document的时间更靠后些的话,就是用当前document的值替换累计器 document的值。因为每个组都有各自的累计器,勿需担心日期的不同对累计器的影响。

先前我们说的是取最近30天的价格,我们可以加一个条件,满足条件的才会处理

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

如果某些document没有day这个键的话,它们就会被归入到day:null这个组,你可以给condition加个条件"day" : {"$exists" : true}来排除这个组。

使用终结器(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;
...     }
...   }
... }})

返回的结果是下边这个样子

[
{
"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}}
]

实际上我们需要的只是值最大的那个标签,并不需要将整个tags返回给客户端,这就是group命令里可选的键"finalize"存在的原因。 finalize指定一个函数,在结果返回给客户端之前,每个分组都会执行一次这个函数。我们使用finalize来去掉不需要的部分。

> db.runCommand({"group" : {
... 
"ns" : "posts",
... 
"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
... }}})

 

使用函数作为分组key

有些情况下,你可能需要更复杂的分组规则,不是一个简单的key,那么你就可以用"$keyf"来定义一个分组函数。

> db.posts.group({"ns" : "posts",
... 
"$keyf" : function(x) { return x.category.toLowerCase(); },
... 
"initializer" : ... })


4.MapReduce

MapReduce可是聚合工具里的高级武器,其他工具能做的它能做,其他工具做不了的它也能做。MapReduce是一个在多个服务器间可以并行执行的聚合方法,它将问题分割成多个块,发送给不同的机器,让每个机器解决自己的部分,当所有的机器都完成之后,把所有的结果都合并起来。(ps:这说的貌似 MapReduce最原初的概念,感觉跟我们下边的内容关系不大)

MapReduce分两步完成,第一步是映射(Map),将document里的键值投射为一组其他的键值对,第二步是精简(Reduce),将投射出来的键值对按照键合并,每个键最后只有一个值。(ps:这是我的理解,书上写的太拗口)

使用MapReduce的代价是速度,group的速度就不咋地,MapReduce更慢,所以一般都是作为后台任务执行,完成之后对其结果collection进行查询。

例子1:找出collection里所有的key

使用MapReduce解决这个问题确实是杀鸡用牛刀,我们主要是看看MapReduce是如何工作的。MongoDB是无结构的,所以它不会跟踪 document里都有哪些key,我们在这个示例里对collection里的每个key的使用次数进行计数,不包括嵌入的document的key。

第一步,映射(Map)使用一个特殊的函数来返回值,这些值后边接下来处理,这个特殊函数就是emit。emit给MapReduce一个key和一个 value,我们这个例子里,我们将document的每个key投射为一个记录其出现次数的数量{count : 1},因为我们要分别记录每个key的出现次数,所以就需要对每个key调用emit函数。

> map = function() {
... 
for (var key in this) {
...   emit(key, {count : 
1});
... }};

现在我们就有了很多的{count : 1},每个都和collection里的一个key关联,相同key的这些{count : 1}构成一个数组被传递给reduce函数,reduce函数有两个参数,第一个是key,就是emit的第一个参数,第二个是数组,包含了被投射在这个 key上的所有{count : 1} 。

> reduce = function(key, emits) {
... total 
= 0;
... 
for (var i in emits) {
...   total 
+= emits[i].count;
... }
... 
return {"count" : total};
... }

对来自映射阶段或者前边的reduce阶段的结果,reduce函数必须能够对其重复调用,所以reduce返回的document必须能够重新传递给reduce函数(作为第二个参数)。

MapReduce函数的调用结果如下:

> mr = db.runCommand({"mapreduce" : "foo""map" : map, "reduce" : reduce})
{
"result" : "tmp.mr.mapreduce_1266787811_1",
"timeMillis" : 12,
"counts" : {
  "input" : 6
  "emit" : 14
  "output" : 5
},
"ok" : true
}
  • "result" : "tmp.mr.mapreduce_1266787811_1"
    存储MapReduce结果的collection的名字,这是个临时的collection,连接关闭后即被删除。我们可以指定一个好听点的名字,并将这个collection永久保存,稍后会讲到。
  • "timeMillis" : 12
    操作花费的时间,单位毫秒
  • "counts" : { ... }
    "input" : 6    传递给map函数的document数量
    "emit" : 14    map函数中调用emit函数的次数
    "output" : 5   结果集collection中document的数量

对结果集collection执行查询就可以看到所有的key和出现次数了

> 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 } }


例子2:对网页分类

假设我们有个网站,用户可以提交通向其他页面的链接,用户可以给链接添加一些标签标明这个链接和特定的主题关联,如"政治","极客”,"icanhascheezburger"等。(ps:icanhascheezburger是个网站,主题内容是些搞笑的猫咪图片,配些文字说明)我们可以用MapReduce找出那些主题是最近流行的。

首先,我们需要一个map函数,根据流行程度和最新程度将标签投射为一个值。

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) {
  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;
};

这样,结果集里就包含了每个标签的一个url列表和一个标识其流行度的总得分。

ps:

我们和关系数据库比较一下更容易看到它的关键之处,关键之处就在于emit函数的第一个参数,sql中使用"group by 字段"进行分组,字段的每个不同值就是一个组,而MapReduce中是使用emit为每个字段不同值创建一个key和一个值的数组。简单说,sql的 group by用的是字段名,emit用的是字段的值。明显MapReduce更加灵活强大一些。


mongoDB和MapReduce

使用MapReduce命令,除了指定mapreduce,map,reduce这三个必须的键之外,还有很多其他的可选的键。

  • "finalize" : function
    终结器函数,接受reduce的输出
  • "keeptemp" : boolean
    连接关闭后是否保存临时结果集collection
  • "output" : string
    输出collection的名字,使用此选项意味着keeptemp为true
  • "query" : document
    查询条件,过滤传递给map函数的document
  • "sort" : document
    发送给map函数前对document进行排序,经常是和limit联用
  • "limit" : integer
    发送给map函数的document的最大数量
  • "scope" : document
    在javascript代码中可以使用的变量
  • "verbose" : boolean
    是否输出更详细的服务器日志

使用scope

如果在MapReduce中使用客户端的值,那就必须使用scope选项了。你只需要传递给scope一个 变量名:值 格式的document就可以了,然后这个值在map,reduce以及finalize函数中就可以使用了。这个变量的值在各个函数中是只读的。

比如,刚才我们第二个例子中计算页面的最新性时使用的是1/(new Date() - this.date),如果我们想不使用new Date(),而是把当前日期传递进来的话,就可以定义个叫now的变量

> db.runCommand({"mapreduce" : "webpages""map" : map, "reduce" : reduce,
"scope" : {now : new Date()}})

然后在map函数里就可以用1/(now - this.date)了。

来源:http://www.open-open.com/lib/view/open1328162150984.html

分享到:
评论

相关推荐

    MongoDB权威指南中文版.pdf

    但根据文件标题《MongoDB权威指南中文版.pdf》和描述内容,我可以推测读者想要了解的是关于MongoDB数据库系统的一些基础知识和概念。 MongoDB是一款流行的开源文档导向数据库系统(NoSQL),它以其高性能、高可用性...

    MongoDB权威指南-中文版.pdf

    通过阅读《MongoDB权威指南》这本书,你可以深入了解MongoDB的原理、安装与配置、数据模型设计、备份与恢复策略、性能调优、安全性等方面的知识,从而更好地运用MongoDB来解决实际问题。无论你是初学者还是经验丰富...

    MongoDB 权威指南.pdf

    ### MongoDB权威指南知识点详解 #### 一、MongoDB概述 MongoDB是一款开源的NoSQL数据库系统,采用BSON(Binary JSON)格式存储文档数据。它支持动态查询、高可用性及横向扩展等特性,适用于大数据量的应用场景。...

    MongoDB权威指南.pdf

    英文版:MongoDB: The Definitive Guide内容简介《MongoDB权威指南》是一本广受好评的MongoDB方面的图书。与传统的关系型数据库不同,MongoDB是一种面向文档的数据库。书中介绍了面向文档的存储方式及利用MongoDB的...

    MongoDB权威指南 中文版高清PDF

    《MongoDB权威指南》是一本深入解析该技术的专业书籍,旨在帮助读者全面理解和掌握MongoDB的使用。 本书首先从数据库的基础概念入手,介绍了NoSQL数据库与传统关系型数据库的区别,阐述了MongoDB作为文档数据库的...

    MongoDB权威指南(中英文两版)

    "MongoDB权威指南"是学习和深入理解这一技术的重要参考资料,提供了全面的理论知识和实践经验。本书分为中文版和英文版,适合不同语言背景的读者,帮助他们掌握MongoDB的核心概念和操作技巧。 在中文版的《MongoDB...

    MongoDB权威指南中文版高清版带书签PDF

    在《MongoDB权威指南》这本书中,作者详细地介绍了MongoDB的基础知识、安装配置、数据模型、查询语言、操作管理以及高级特性。以下是对这本书中核心知识点的概述: 1. **MongoDB基础知识**:MongoDB是NoSQL数据库的...

    MongoDB权威指南中文版

    《MongoDB权威指南中文版》第1章 简介 第2章 入门 第3章 创建、更新及删除文档 第4章 查询 第5章 索引 第6章 聚合 第7章 进阶指南 第8章 管理 第9章 复制 第10章 分片 第11章 应用举例 附录A 安装MongoDB 附录B ...

    MongoDB权威指南(中文版)

    《MongoDB权威指南》是一本全面深入探讨MongoDB数据库系统的专著,中文版的发布使得国内用户能够更方便地学习和理解这一强大的NoSQL数据库。MongoDB作为一种分布式文档型数据库,近年来在处理大规模数据存储和高并发...

    别再用了MongoDB权威指南 中文版

    MongoDB是一种流行的开源、分布式文档型数据库,常用于处理大规模数据。尽管随着MySQL等传统关系型...而《MongoDB权威指南》这样的资源,将帮助读者深入理解MongoDB的原理与实践,以便更好地利用这一强大的数据库技术。

    MongoDB权威指南(中文版)高清

    通过本书的权威解读,你会了解面向文档数据库的诸多优点,会发现MongoDB如此稳定、性能优越甚至能够无限水平扩展背后的原因。本书的两位作者均来自开发并支持开源数据库MongoDB的公司10gen。数据库开发人员可将此书...

    mongodb权威指南(第二版)

    综上所述,根据标题、描述、标签和部分内容所透露的信息,可以构建关于MongoDB权威指南(第二版)的知识点。这些知识点不仅涵盖了MongoDB的关键概念和操作,还涉及到了该书的出版信息和更新情况,这对于数据库管理员...

    MongoDB权威指南高清中英文两版PDF

    这份"MongoDB权威指南高清中英文两版PDF"涵盖了MongoDB的基础到高级概念,是学习和掌握这一数据库系统的重要参考资料。 首先,中文版与英文版的结合使得无论对于初级还是高级用户,都能根据自己的语言习惯进行学习...

    Mongo DB 权威指南(中文版)

    ### MongoDB权威指南(中文版)知识点总结 #### 一、MongoDB概述 1. **定义与特点**:MongoDB是一款开源的文档型数据库系统,它使用JSON格式的文档存储数据,支持动态模式,使得开发人员可以轻松地进行数据操作。...

    MongoDB权威指南第2版中文版带目录

    《MongoDB权威指南第2版》是深入理解和应用MongoDB的重要参考资料,书中详细介绍了MongoDB的基础知识、高级特性和最佳实践。 本书首先会讲解MongoDB的基本概念,包括数据库、集合、文档以及数据模型的设计。MongoDB...

Global site tag (gtag.js) - Google Analytics