mongoDB--mapreduce用法详解(经典)

Map-Reduce部分:Map-Reduce相当于关系型数据库中的group by,主要用于统计数据之用。

MongoDB之MapReduce

先引入一个问题:
在MongoDB javascript Shell中对Array对象进行了一些扩展,其中新增sum方法,以方便统计数据之用的。

>Array.sum
function(arr){
if(arr.length == 0)
return null;
var s = arr[0];
for(var i = 1; i < arr.length; i++)
s += arr[i];
return s;
}

而在浏览器端,js原生没有为Array提供sum方法的。
Array.sum为undefined。

进入正题,先以例子作为切入口:
先插入一些测试用的数据

>db.mythings.insert({location:'Guangzhou', age:20, name:'j'})
>db.mythings.insert({location:'Guangzhou', age:21, name:'ji'})
>db.mythings.insert({location:'Beijing', age:22, name:'jim'})
>db.mythings.insert({location:'Beijing', age:23, name:'jimv'})
>db.mythings.insert({location:'Guangzhou', age:25, name:'jimvi'})
>db.mythings.insert({location:'Shanghai', age:25, name:'jimvin'})

需求1:统计不同地方(‘Guangzhou,Beijing,Shanghai’)的人的岁数总和

>var map = function(){ emit(this.location, this.age); }
>var reduce = function( key, values ){ return Array.sum(values); }
>var options = { out: "age_totals" }
>db.mythings.mapReduce( map, reduce, options )

显示结果:

>db.age_totals.find()
{
"_id": "Shanghai",
"value": 25
}
{
"_id": "Guangzhou",
"value": 66
}
{
"_id": "Beijing",
"value": 45
}

需求2:统计不同地方(‘Guangzhou,Beijing,Shanghai’)的人数总和
 

>var map = function(){ emit(this.location, 1); }
>var reduce = function( key, values ){ return Array.sum(values); }
>var options = { out: "person_totals" }
>db.mythings.mapReduce( map, reduce, options )

显示结果:

>db.person_totals.find()
{
"_id": "Shanghai",
"value": 1
}
{
"_id": "Guangzhou",
"value": 3
}
{
"_id": "Beijing",
"value": 2
}

需求3:统计不同地方(‘Guangzhou,Beijing,Shanghai’)的人名列表

>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return values.join(', '); }
>var options = { out: "name_totals" }
>db.mythings.mapReduce( map, reduce, options )

显示结果:

>db.name_totals.find()
{
"_id": "Shanghai",
"value": "jimviv"
}
{
"_id": "Guangzhou",
"value": "j, ji, jimvi"
}
{
"_id": "Beijing",
"value": "jim, jimv"
}

需求4:统计不同地方(‘Guangzhou,Beijing,Shanghai’),并且年龄在25岁(不包括25岁)以下的人名列表

>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>var options = { query: { age: {$lt: 25} }, out: "name_totals" }
>db.mythings.mapReduce( map, reduce, options )

显示结果:

>db.name_totals.find()
{
"_id": "Guangzhou",
"value": "Guangzhou: j, ji"
}
{
"_id": "Beijing",
"value": "Beijing: jim, jimv"
}

分析一下:
1. map部分
作用:用于分组的。
emit(param1, param2)
param1:需要分组的字段,this.字段名。
param2:需要进行统计的字段,this.字段名。

2. reduce部分
作用:处理需要统计的字段
var reduce = function(key, values){
......统计字段处理
}
key: 指分组字段(emit的param1)对应的值
values:指需要统计的字段(emit的param2)值组成的数组

简单介绍统计常用的方法:
* 对数值类型进行求和

var reduce = function(key, values){
return Array.sum(values);
}

* 对字符串类型进行拼凑

var reduce = function(key, values){
return values.join(', ');
}

3. options部分
{ query: { age: {$lt: 25} }, out: "name_totals" }
query:先筛选符合条件的记录出来,再进行分组统计。
out:将分组统计后的结果输出到哪个集合当中。
默认情况下,out所指定的集合在数据库断开连接后再次打开时,依旧存在,并保留之前的所有记录的。

4. 执行分组统计
>db.集合名.mapReduce( map, reduce, options )

第二种写法(测试数据同上):

>db.mythings.ensureIndex({location:1, name:-1})
>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,keeptemp: false
,query: { age:{ $lt: 25 }}
,sort:{ location:1, name:-1 }
}
)

显示结果:

>db.a.find()
{
"_id": "Guangzhou",
"value": "Guangzhou: ji, j"
}
{
"_id": "Beijing",
"value": "Beijing: jimv, jim"
}

value的名字列表“ji,j” 和 “jim,jim” 都进行了降序排列,证明使用了sort功能。

解析:
mapreduce:
分组统计的集合名
eg:
mapreduce: 'mythings'
不能写成mapreduce: mythings,否则报异常:mythings is not defined

注意:以下写法错误

>var collection = db.mythings.find( { age: {$lt: 25} } )
>db.runCommand(
{
mapreduce:'collection'
,map: map
,reduce: reduce
,out: 'a'
,keeptemp: false
}
)

报错:{ "ok": 0, "errmsg": "ns doesn't exist" }

map,reduce :
同上,不做阐述

out :
将分组统计结果输出到某个集合。
注意:不能缺省,必须指定名称,否则报错,报错如下:
“exception: 'out' has to be a string or an object”

keeptemp :
是否保留临时集合(指out指定的集合)
keeptemp:false时会在数据库断开连接后,MongoDB会移除该集合的所有记录。而不是删除。
keeptemp:true时即使数据库断开连接后,再次连接上,该临时集合依旧保持之前所有记录。
keeptemp默认值为true。

query :
筛选记录后,再进行分组统计
eg:
query: { age:{ $lt: 25 }}

sort :
对分组统计的集合进行排序,也即先排序,后再执行分组统计的。
注意:这里的排序需要用到索引,必须先创建索引。
eg:

>db.mythings.ensureIndex({location:1, name:-1})
>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,sort:{ location:1, name:-1 }
}
)
limit :对分组统计的集合先进行限制返回记录的条数,然后再去进行统计操作。注意:不要理解成对统计后的结果进行限制返回记录条数。
>db.mythings.ensureIndex({location:1, name:-1})
>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,keeptemp: false
,query: { age:{ $lt: 25 }}
,sort:{ location:1, name:-1 }
,limit:3
}
)

结果:

{
"_id": "Guangzhou",
"value": "ji"
}
{
"_id": "Beijing",
"value": "Beijing: jimv, jim"
}

verbose :
显示时间统计信息,取值为true/false
eg:

>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,verbose: true
}
)

在cmd控制台返回了以下结果

{
"result":"a"
“timeMillis”:3
“timeing”:{
“mapTime”: 0
,“emitLoop”: 2
,“reduceTime”: 0
,“mode”: "mixed"
,“total”: 3
}
.....
}
其中“timeing”就是对应的详细耗时信息。
finalize:
function(key, reducedValue){
return obj;
}

key: 指分组字段(emit的param1)对应的值
reducedValue:指reduce返回的结果

eg:

>var final = function(key, reducedValue){
var obj = {};
obj.key = key;
obj.reducedValue = reducedValue
return obj;
}
>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,finalize: final
}
)

结果:

{
"_id": "Guangzhou",
"value": {
"key": "Guangzhou",
"reducedValue": "Guangzhou: ji, j"
}
}
{
"_id": "Beijing",
"value": {
"key": "Beijing",
"reducedValue": "Beijing: jimv, jim"
}
}

scope:
为finalize、map、reduce指定this指针的作用域。
注意:不是像MongoDB权威指南中文版.pdf 所说的为finalize、map、reduce导入外部变量。

eg:

>var final = function(key, reducedValue){
var obj = {};
obj.scope = this.scope;
obj.key = key;
obj.reducedValue = reducedValue
return obj;
}
>var map = function(){ emit(this.location, this.name); }
>var reduce = function( key, values ){ return key + ': ' + values.join(', '); }
>db.runCommand(
{
mapreduce:'mythings'
,map: map
,reduce: reduce
,out: 'a'
,finalize: final
,scope:{scope:4}
}
)

结果

{
"_id": "Guangzhou",
"value": {
"scope": 4,
"key": "Guangzhou",
"reducedValue": "Guangzhou: ji, j"
}
}
{
"_id": "Beijing",
"value": {
"scope": 4,
"key": "Beijing",
"reducedValue": "Beijing: jimv, jim"
}
}
  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值