0%

MongoDb 上使用 Map-Reduce 来统计数据 (doctrine 使用例子)

MongoDb 上使用 Map-Reduce 来统计数据,根据场景的变化使用,如 Mysql 的 group by

MongoDb 的 Map-Reduce 使用 (Doctrine)

使用场景, Collection 上根据某个字段进行统计,如 Mysql 的 group by

(1)MapReduce使用自定义 JavaScript 函数执行 map 和 reduce 操作,所以是基于js引擎,单线程执行,效率不高,比 Aggregation 复杂,适合用做后台统计等。

(2)MapReduce 支持分片操作,可以进行拆分,分发到不同的机器上执行(多服务器并行做数据集合处理),然后再将不同的机器处理的结果汇集起来输出结果。

(3)MapReduce能执行单一聚合的所有操作 count、distinct、group,但 group 在当数据量非常大的时候,处理能力就不太好,先筛选再分组,不支持 分片,对数据量有所限制,效率不高。

MongoDb 官方(带图片很好理解)
MongoODM Doctrine (Doctrine的例子,只有一个用例)
Example
Example

JsonMongo

数据:

1
2
3
4
5
{"_id" : ObjectId("59fa71d71fd59c3b2cd908d7"),"name" : "鲁迅","book" : "呐喊","price" : 38.0,"publisher" : "人民文学出版社"}
{"_id" : ObjectId("59fa71d71fd59c3b2cd908d8"),"name" : "曹雪芹","book" : "红楼梦","price" : 22.0,"publisher" : "人民文学出版社"}
{"_id" : ObjectId("59fa71d71fd59c3b2cd908d9"),"name" : "钱钟书","book" : "宋诗选注","price" : 99.0,"publisher" : "人民文学出版社"}
{"_id" : ObjectId("59fa71d71fd59c3b2cd908da"),"name" : "钱钟书","book" : "谈艺录","price" : 66.0,"publisher" : "三联书店"}
{"_id" : ObjectId("59fa71d71fd59c3b2cd908db"),"name" : "鲁迅","book" : "彷徨","price" : 55.0,"publisher" : "花城出版社"}
1
2
3
4
5
var map=function(){emit(this.name,this.price)}
var reduce=function(key,value){return Array.sum(value)}
var options={out:"totalPrice"}
db.sang_books.mapReduce(map,reduce,options);
db.totalPrice.find()

emit 函数主要用来实现分组,接收两个参数,第一个参数表示分组的字段,第二个参数表示要统计的数据,
reduce来做具体的数据处理操作,接收两个参数,对应 emit 方法的两个参数,这里使用了Array中的sum函数对price字段进行自加处理。
option out:”totalPrice” 将查询出来的所有数据输出到 totalPrice 这个 Collection

1
2
3
4
5
6
7
8
9
10
11
12
{
"_id" : "曹雪芹",
"value" : 22.0
}
{
"_id" : "钱钟书",
"value" : 165.0
}
{
"_id" : "鲁迅",
"value" : 93.0
}
  • 比如查询每个人售价在¥40以上的书:
1
2
3
4
5
var map=function(){emit(this.name,this.book)}
var reduce=function(key,value){return value.join(',')}
var options={query:{price:{$gt:40}},out:"books"}
db.sang_books.mapReduce(map,reduce,options);
db.books.find()

这里的 options 带有 query,可以刷选数据。

  • 使用 finalize 操作
1
2
3
4
5
var f1 = function(key,reduceValue){var obj={};obj.author=key;obj.books=reduceValue; return obj}
var map=function(){emit(this.name,this.book)}
var reduce=function(key,value){return value.join(',')}
db.runCommand({mapreduce:'sang_books',map,reduce,out:"books",finalize:f1})
db.books.find()

这里使用了 options 上的 finalize,对最后的数据进行处理

result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"_id" : "曹雪芹",
"value" : {
"author" : "曹雪芹",
"books" : "红楼梦"
}
}
{
"_id" : "钱钟书",
"value" : {
"author" : "钱钟书",
"books" : "宋诗选注,谈艺录"
}
}
{
"_id" : "鲁迅",
"value" : {
"author" : "鲁迅",
"books" : "呐喊,彷徨"
}
}
  • 若统计数量 map 可以这样写

    var map=function(){emit(this.name,1)}

  • options 可以有多个参数,上面使用
1
2
3
4
5
6
7
8
9
10
11
{
out: <collection>,
query: <document>,
sort: <document>,
limit: <number>,
finalize: <function>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>
}

Doctrine

  • 根据 collection Product 统计出 brand 的出现次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$qb = $this->dm->createQueryBuilder(Product::class)
->field('tags.type')
->notEqual('brand')
->map($this->getMapFunction())
->reduce($this->getReduceFunction());
->mapReduceOptions([
'out'=>'brand_map_reduce_result' //output to collection
]);

$results = $qb->getQuery()->execute();

private function getMapFunction()
{
return 'function() { emit(this.brand,1); }';
}

private function getReduceFunction()
{
return 'function(key, values) { return Array.sum(values) }';
}

以上代码是统计有多少商品同品牌,然后输出到 brand_map_reduce_result

  • 根据 Collection 下有个 tags 的数组,统计出每个 tag 的出现次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$qb = $this->dm->createQueryBuilder(Product::class)
->map($this->getMapFunction())
->reduce($this->getReduceFunction());
->mapReduceOptions([
'out' => 'map_reduce_tag_result' //output to collection
]);


private function getMapFunction()
{
return 'function() {
for (var idx = 0; idx < this.original_tags.length; idx++) {
var value = 1;
emit(this.original_tags[idx], value);
}
}';
}

private function getReduceFunction()
{
return 'function(key, values) { return Array.sum(values)}';
}