mongodb mapreduce主要方法详解

mongodb在分组统计用途比较大,相当于mysql里面的group by,但比group by 功能更强大。

db.collection.mapReduce( //要操作的目标集合
	<map>,//映射函数 (生成键值对序列,作为 reduce 函数参数)
	<reduce>,//统计函数
	{
		out: <collection>,//统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。必须要小于BSON文档的大小(16M)限制
		query: <document>,//目标记录过滤。
		sort: <document>,//目标记录排序。
		limit: <number>,//限制目标记录数量。
		finalize: <function>,//最终处理函数 (对 reduce 返回结果进行最终整理后存入结果集合)。
		scope: <document>,//向 map、reduce、finalize 导入外部变量。
		jsMode: <boolean>,//从BSON转化为JSON,执行Map过程,将JSON转化为BOSN,从BSON转化为JSON,执行Reduce过程,将JSON转化为BSON,注意,jsMode 受到JSON堆大小和独立主键最大500KB的限制。因此,对于较大的任务jsMode并不适用,在这种情况下会转变为通常的模式
		verbose: <boolean>,//显示详细的时间统计信息。
		bypassDocumentValidation: <boolean>
	}
)

数据准备

db.emp.insert({level:"t1", age:24, name:'Tom'});
db.emp.insert({level:"t2", age:22, name:'Jacky'});
db.emp.insert({level:"t3", age:26, name:'Lily'});
db.emp.insert({level:"t2", age:29, name:'Tony'});
db.emp.insert({level:"t3", age:21, name:'Harry'});
db.emp.insert({level:"t4", age:35, name:'Vincent'});
db.emp.insert({level:"t2", age:25, name:'Bill'});
db.emp.insert({level:"t3", age:25, name:'Bruce'});
db.emp.insert({level:"t1", age:25, name:'tencent'});
db.emp.insert({level:"t4", age:25, name:'baidu'});
map
map函数必须调用 emit(key, value) 返回键值对。map里面有如下限制和要求:
1:In the map function, reference the current document as this within the function.
this为当前处理的文档
2:The map function should not access the database for any reason.
不应该访问任何非this的其它集合,事实上也没办法访问,用db变量访问会直接报错
3:The map function should be pure, or have no impact outside of the function (i.e. side effects.)
不能的影响其它功能
4:A single emit can only hold half of MongoDB’s maximum BSON document size.
emit的容量只能试文档容量的一半,也是8MB,理论上讲分组后不会存在这么大的文档。
5:The map function may optionally call emit(key,value) any number of times to create an output document associating key with value.

reduce
通过map运算后,key为分组字段,其value的集合(即数组)会传给reduce函数执行
特别注意:若value集合长度小于2,则该分组不会传到reduce函数执行,任何需要在reduce里面对key就行统计操作的,需要特别注意,有可能会以后key。上面的示例数据,可以删掉最后两条看看效果。t1和t4并不会再reduce里面出现。

finalize
利用 finalize() 我们可以对 reduce() 的结果做进一步处理。处理完毕后要返回给存储,可以自定义结果。

案例
公司职员有t1,t2,t3,t4不同的职位等级,有职员名字和年龄。
现在需要统计不同的职位的人员的个数

m = function () {
	total = total +1
	// if(this.age==25){
		emit(this.level, 1);
	// }
	print("total in map:"+total)
}
r = function (key, values) {
	total = total +1
	var x = 0;
	print(separator)
	print(key+separator+values)
	// print(values)
	values.forEach(function (v) {
		x += v;
		// print(v);
	});
	print("total in reduce:"+total)
	return x;
}
res = db.runCommand({
	mapreduce:"emp",
	map:m,
	reduce:r,
	// query:{"age":25},
	// sort:{"age":-1},
	// limit:2,
	// verbose:true,
	finalize:function(key, reducedValue) {
		total = total +1
		print(key+separator+reducedValue)
		print("total in finalize:"+total)
		return reducedValue;
		// return {level:key,count:reducedValue};
	},
	scope:{separator:"\t",total:0},
	out:"emp_result"
});

其中打印的日志过程为:
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:1
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:2
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:3
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:4
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:5
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:6
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:7
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:8
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:9
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:10
2016-12-12T20:38:05.537+0800 I - [conn160]
2016-12-12T20:38:05.537+0800 I - [conn160] t1 1,1
2016-12-12T20:38:05.537+0800 I - [conn160] total in reduce:11
2016-12-12T20:38:05.537+0800 I - [conn160]
2016-12-12T20:38:05.537+0800 I - [conn160] t2 1,1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:12
2016-12-12T20:38:05.538+0800 I - [conn160]
2016-12-12T20:38:05.538+0800 I - [conn160] t3 1,1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:13
2016-12-12T20:38:05.538+0800 I - [conn160]
2016-12-12T20:38:05.538+0800 I - [conn160] t4 1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:14
2016-12-12T20:38:05.539+0800 I - [conn160] t1 2
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:15
2016-12-12T20:38:05.539+0800 I - [conn160] t2 3
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:16
2016-12-12T20:38:05.539+0800 I - [conn160] t3 3
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:17
2016-12-12T20:38:05.539+0800 I - [conn160] t4 2
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:18
统计的结果为:

/* 1 */
{
"_id" : "t1",
"value" : 2
}

/* 2 */
{
"_id" : "t2",
"value" : 3
}

/* 3 */
{
"_id" : "t3",
"value" : 3
}

/* 4 */
{
"_id" : "t4",
"value" : 2
}

通过例子可以总结为(我自己的理解,有可能是错的):

1.大致的执行过程为map->过滤条件->reduce->限制条件->finalize
2.query不是必须的,可以在map里面也可以进行过滤
3.通过print可以打印,在日志里面查看打印结果
4.通过日志(drop applock.tmp.mr.emp_59)可以看出,中间的临时结果会被删除
5.scope的变量是全局变量,可以在map,reduce,finalize中间共享

标签: none

添加新评论