MongoDB:19-MongoDB-Map Reduce

 
 
  1. Map-Reduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行然后再将结果合并成最终结果(REDUCE
  2. MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。

MapReduce 命令

  • 以下是MapReduce的基本语法:

  
  
  1. >db.collection.mapReduce(
  2. function() {emit(key,value);}, //map 函数
  3. function(key,values) {return reduceFunction}, //reduce 函数
  4. {
  5. out: collection,
  6. query: document,
  7. sort: document,
  8. limit: number
  9. }
  10. )
   
   
  1. db.collection.mapReduce(
  2. <map>,
  3. <reduce>,
  4. {
  5. out: <collection>,
  6. query: <document>,
  7. sort: <document>,
  8. limit: <number>,
  9. finalize: <function>,
  10. scope: <document>,
  11. jsMode: <boolean>,
  12. verbose: <boolean>,
  13. bypassDocumentValidation: <boolean>
  14. }
  15. )
   
   
  1. MapReduce要实现两个函数:MapReduce
  2. Map函数调用emit(key,value)遍历一个或多个集合中所有的记录,进行分组(group by),
  3. 然后将keyvalue传给Reduce函数进行处理,输出结果。
  4. 1MapReduce使用自定义JavaScript函数执行mapreduce操作,所以是基于js引擎,单线程执行,效率不高,
  5. Aggregation复杂,适合用做后台统计等。
  6. 2MapReduce支持分片操作,可以进行拆分,分发到不同的机器上执行(多服务器并行做数据集合处理),
  7. 然后再将不同的机器处理的结果汇集起 来,输出结果,。
  8. 3MapReduce能执行单一聚合的所有操作countdistinctgroup
  9. group 在当数据量非常大的时候,处理能力就不太好,先筛选再分组,不支持 分片,对数据量有所限制,效率不高。

参数说明:
  •      
         
    1. 参数说明:
    2. map:是JavaScript 函数,负责将每一个输入文档转换为零或多个文档,通过key进行分组,生成键值对序列,作为 reduce 函数参数。
    3. reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-values变成key-value,也就是把values数组变成一个单一的值value)。
    4. outreduce执行完,存放的集合,如果不指定集合,则使用默认的临时集合,在MapReduce的连接关闭后自动就被删除了。
      • out: { <action>: <collectionName>
      • [, db: <dbName>]
      • [, sharded: <boolean> ]
      • [, nonAtomic: <boolean> ] }
    5. query:过滤的条件,对符合条件的文档执行map函数。(querylimitsort可以随意组合)。
    6. sort :对文档进行排序,sortlimit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制。
    7. limit :发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)。
    8. finalize:可以对reduce输出结果再一次修改,跟groupfinalize一样,不过MapReduce没有group4MB文档的输出限制。
    9. scope:向mapreducefinalize导入外部变量。
    10. verbose:是否包括结果信息中的时间信息,默认为fasle
    11. "timing" : {
    12. "mapTime" : 0,
    13. "emitLoop" : 2,
    14. "reduceTime" : 0,
    15. "mode" : "mixed",
    16. "total" : 0
    17. }

以下实例在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。



使用 MapReduce

  • 考虑以下文档结构存储用户的文章,文档存储了用户的 user_name 和文章的 status 字段:

  
  
  1. db.posts.insert([
  2. {
  3. "post_text": "Spring",
  4. "user_name": "fly",
  5. "status":"active"
  6. },
  7. {
  8. "post_text": "Struts2",
  9. "user_name": "fly",
  10. "status":"active"
  11. },
  12. {
  13. "post_text": "Hibernate",
  14. "user_name": "fly",
  15. "status":"disabled"
  16. },
  17. {
  18. "post_text": "Mybatis",
  19. "user_name": "fly",
  20. "status":"active"
  21. },
  22. {
  23. "post_text": "Redis",
  24. "user_name": "fly",
  25. "status":"disabled"
  26. },
  27. {
  28. "post_text": "Restful",
  29. "user_name": "cc",
  30. "status":"active"
  31. },
  32. {
  33. "post_text": "Dubbo",
  34. "user_name": "cc",
  35. "status":"disabled"
  36. },
  37. {
  38. "post_text": "SpringCloud",
  39. "user_name": "cc",
  40. "status":"active"
  41. },
  42. {
  43. "post_text": "SpringBoot",
  44. "user_name": "cc",
  45. "status":"disabled"
  46. },
  47. {
  48. "post_text": "MongoDB",
  49. "user_name": "cc",
  50. "status":"active"
  51. },
  52. {
  53. "post_text": "MySQL",
  54. "user_name": "cc",
  55. "status":"active"
  56. }
  57. ])

  • 现在,我们将在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status:"active"),并通过user_name分组,计算每个用户的文章数:

  
  
  1. >db.posts.mapReduce(
  2. function() { emit(this.user_name,1); },
  3. function(key, values) {return Array.sum(values)},
  4. {
  5. query:{status:"active"},
  6. out:"post_total"
  7. }
  8. )

  • 以上 mapReduce 输出结果为:

  
  
  1. /* 1 */
  2. {
  3. "result" : "post_total",
  4. "timeMillis" : 409.0,
  5. "counts" : {
  6. "input" : 7,
  7. "emit" : 7,
  8. "reduce" : 2,
  9. "output" : 2
  10. },
  11. "ok" : 1.0,
  12. "_o" : {
  13. "result" : "post_total",
  14. "timeMillis" : 409,
  15. "counts" : {
  16. "input" : 7,
  17. "emit" : 7,
  18. "reduce" : 2,
  19. "output" : 2
  20. },
  21. "ok" : 1.0
  22. },
  23. "_keys" : [
  24. "result",
  25. "timeMillis",
  26. "counts",
  27. "ok"
  28. ],
  29. "_db" : {
  30. "_mongo" : {
  31. "slaveOk" : true,
  32. "host" : "localhost:27017",
  33. "defaultDB" : "mongotest",
  34. "authStatus" : {
  35. "authRequired" : true,
  36. "isMaster" : true,
  37. "replSetGetStatus" : true
  38. },
  39. "_readMode" : "commands",
  40. "_writeMode" : "commands"
  41. },
  42. "_name" : "mongotest"
  43. },
  44. "_coll" : {
  45. "_mongo" : {
  46. "slaveOk" : true,
  47. "host" : "localhost:27017",
  48. "defaultDB" : "mongotest",
  49. "authStatus" : {
  50. "authRequired" : true,
  51. "isMaster" : true,
  52. "replSetGetStatus" : true
  53. },
  54. "_readMode" : "commands",
  55. "_writeMode" : "commands"
  56. },
  57. "_db" : {
  58. "_mongo" : {
  59. "slaveOk" : true,
  60. "host" : "localhost:27017",
  61. "defaultDB" : "mongotest",
  62. "authStatus" : {
  63. "authRequired" : true,
  64. "isMaster" : true,
  65. "replSetGetStatus" : true
  66. },
  67. "_readMode" : "commands",
  68. "_writeMode" : "commands"
  69. },
  70. "_name" : "mongotest"
  71. },
  72. "_shortName" : "post_total",
  73. "_fullName" : "mongotest.post_total"
  74. }
  75. }

  • 结果表明,共有 7个符合查询条件(status:"active")的文档, 在map函数中生成了 7个键值对文档,最后使用reduce函数将相同的键值分为 2 组。
  • 具体参数说明:

  
  
  1. resultreduce执行完,存放的集合,如果不指定集合,则使用默认的临时集合,在MapReduce的连接关闭后自动就被删除了。
  2. timeMillis执行花费的时间,毫秒为单位
  3. input满足条件被发送到map函数的文档个数
  4. emitmap函数中emit被调用的次数,也就是所有集合中的数据总量
  5. ouput结果集合中的文档个数(count对调试非常有帮助)
  6. ok是否成功,成功为1
  7. err如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大

  • 使用 find 操作符来查看 mapReduce 的查询结果:

  
  
  1. >db.posts.mapReduce(
  2. function() { emit(this.user_name,1); },
  3. function(key, values) {return Array.sum(values)},
  4. {
  5. query:{status:"active"},
  6. out:"post_total"
  7. }
  8. ).find()

  • 以上查询显示如下结果,两个用户 tom 和 mark 有两个发布的文章:

  
  
  1. /* 1 */
  2. {
  3. "_id" : "cc",
  4. "value" : 4.0
  5. }
  6. /* 2 */
  7. {
  8. "_id" : "fly",
  9. "value" : 3.0
  10. }

  • 用类似的方式,MapReduce可以被用来构建大型复杂的聚合查询。
  • Map函数和Reduce函数可以使用 JavaScript 来实现,使得MapReduce的使用非常灵活和强大。

附录:参数详解
  • map函数
   
   
  1. map函数】
  2. mapJavaScript 函数,负责将每一个输入文档转换为零或多个文档,通过key进行分组,生成键值对序列,作为 reduce 函数参数。
  3.        
           
    1. function() {
    2. emit(key, value);
    3. }

  4. key对文档进行分组,value是要统计的数据,value可以是JSON对象(emit只能容纳MongoDB的最大BSON文件大小的一半)。

  5. 我们对订单的详细统计每个产品类型卖出了多少个。


  • 订单数据    
    
    
  1. db.item.insert( [
  2. {
  3. "quantity" : 2,
  4. "price" : 5.0,
  5. "pnumber" : "p003"
  6. },{
  7. "quantity" : 2,
  8. "price" : 8.0,
  9. "pnumber" : "p002"
  10. },{
  11. "quantity" : 1,
  12. "price" : 4.0,
  13. "pnumber" : "p002"
  14. },{
  15. "quantity" : 2,
  16. "price" : 4.0,
  17. "pnumber" : "p001"
  18. },{
  19. "quantity" : 4,
  20. "price" : 10.0,
  21. "pnumber" : "p003"
  22. },{
  23. "quantity" : 10,
  24. "price" : 20.0,
  25. "pnumber" : "p001"
  26. },{
  27. "quantity" : 10,
  28. "price" : 20.0,
  29. "pnumber" : "p003"
  30. },{
  31. "quantity" : 5,
  32. "price" : 10.0,
  33. "pnumber" : "p002"
  34. }
  35. ])
     
     
  1. 我们先通过 pnumber进行分组,然后在对 quantity相加
  2. 相当于select pnumber,sum(quantity) from item group by pnumber
      
      
  1. var map = function() { emit(this.pnumber,this.quantity)}
  2. var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
  3. db.item.mapReduce( map,
  4. reduce,
  5. { out: "map_reduce_data" }
  6. )
  7. db.map_reduce_data.find()
  • 输出结果
       
       
  1. /* 1 */
  2. {
  3. "_id" : "p001",
  4. "value" : {
  5. "pumber" : "p001",
  6. "quantity" : 12.0
  7. }
  8. }
  9. /* 2 */
  10. {
  11. "_id" : "p002",
  12. "value" : {
  13. "pumber" : "p002",
  14. "quantity" : 8.0
  15. }
  16. }
  17. /* 3 */
  18. {
  19. "_id" : "p003",
  20. "value" : {
  21. "pumber" : "p003",
  22. "quantity" : 16.0
  23. }
  24. }
  • query过滤的条件
        
        
  1. 对符合条件的文档将会执行map函数。(querylimitsort可以随意组合), 
  2. 我们对订单的详细的每次每种产品卖出的数量要大于5的并统计每个产品类型卖出了多少个。
  3. 我们先通过 pnumber进行分组,然后在对 quantity相加 
  4. 相当于select pnumber,sum(quantity) from item where quantity>5 group by pnumber
         
         
  1. var map = function() { emit(this.pnumber,this.quantity)}
  2. var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
  3. db.item.mapReduce( map,
  4. reduce,
  5. { query:{'quantity':{$gt:5}},
  6. out: "map_reduce_data" }
  7. )
          
          
  1. db.map_reduce_data.find()
           
           
  1. /* 1 */
  2. {
  3. "_id" : "p001",
  4. "value" : 10.0
  5. }
  6. /* 2 */
  7. {
  8. "_id" : "p003",
  9. "value" : 10.0
  10. }
  • value是JSON对象
        
        
  1. value可以是JSON格式,我们对订单的详细统计每个产品类型出现次数。
  2. 我们先通过 pnumber进行分组,然后在对 quantity相加 相当于select pnumber,count(*) from item group by pnumber
         
         
  1. var map = function() {emit(this.pnumber,{count:1});}
  2. var reduce=function(key,values){
  3. var count=0;
  4. values.forEach(function(val){ count+=val.count;});
  5. return {'pumber':key,"count":count};
  6. }
  7. db.item.mapReduce( map,
  8. reduce,
  9. { out: "map_reduce_data" }
  10. )
          
          
  1. db.map_reduce_data.find()
           
           
  1. /* 1 */
  2. {
  3. "_id" : "p001",
  4. "value" : {
  5. "pumber" : "p001",
  6. "count" : 2.0
  7. }
  8. }
  9. /* 2 */
  10. {
  11. "_id" : "p002",
  12. "value" : {
  13. "pumber" : "p002",
  14. "count" : 3.0
  15. }
  16. }
  17. /* 3 */
  18. {
  19. "_id" : "p003",
  20. "value" : {
  21. "pumber" : "p003",
  22. "count" : 3.0
  23. }
  24. }
  • emit多次的循环
        
        
  1. 可以对emit多次的循环,可以根据输入文档的项目字段中的元素的数量(键,值)多次调用:
  2.             
                
    1. function() {
    2. this.items.forEach(function(item){ emit(key, value); });
    3. }
  • 数据:    
         
         
  1. db.orders.insert( [
  2. {
  3. "onumber" : "001",
  4. "item" : [{
  5. "quantity" : 2,
  6. "price" : 5.0,
  7. "pnumber" : "p003"
  8. },{
  9. "quantity" : 2,
  10. "price" : 8.0,
  11. "pnumber" : "p002"
  12. }]
  13. },{
  14. "onumber" : "002",
  15. "item" : [{
  16. "quantity" : 1,
  17. "price" : 4.0,
  18. "pnumber" : "p002"
  19. },{
  20. "quantity" : 2,
  21. "price" : 4.0,
  22. "pnumber" : "p001"
  23. },{
  24. "quantity" : 4,
  25. "price" : 10.0,
  26. "pnumber" : "p003"
  27. }]
  28. },{
  29. "onumber" : "003",
  30. "item" : [{
  31. "quantity" : 10,
  32. "price" : 20.0,
  33. "pnumber" : "p001"
  34. },{
  35. "quantity" : 10,
  36. "price" : 20.0,
  37. "pnumber" : "p003"
  38. }]
  39. },{
  40. "onumber" : "004",
  41. "item" : [{
  42. "quantity" : 5,
  43. "price" : 10.0,
  44. "pnumber" : "p002"
  45. }]
  46. }
  47. ])
          
          
  1. 我们对统计订单中对应的产品销售了多少个,我们先通过 pnumber进行分组,然后在对 quantity相加。
         
         
  1. var map = function() { this.item.forEach(function(it){ emit(it.pnumber,it.quantity); })}
  2. var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
  3. db.orders.mapReduce( map,
  4. reduce,
  5. { out: "map_reduce_data" }
  6. )
  • 输出结果:
         
         
  1. db.map_reduce_data.find()
          
          
  1. /* 1 */
  2. {
  3. "_id" : "p001",
  4. "value" : {
  5. "pumber" : "p001",
  6. "quantity" : 12.0
  7. }
  8. }
  9. /* 2 */
  10. {
  11. "_id" : "p002",
  12. "value" : {
  13. "pumber" : "p002",
  14. "quantity" : 8.0
  15. }
  16. }
  17. /* 3 */
  18. {
  19. "_id" : "p003",
  20. "value" : {
  21. "pumber" : "p003",
  22. "quantity" : 16.0
  23. }
  24. }
           
           
  1. > var map = function() { this.item.forEach(function(it){ emit(it.pnumber,it.quantity); })}
  2. 也可以这样写
  3. > var map = function() { for(var i=0;i<this.item.length;i++){ var it=item[i]; emit(it.pnumber,it.quantity); }}
  • reduce函数
             
             
  1. reduceJavaScript 函数,对map操作的输出做合并的化简的操作
  2. (将key-values变成key-value,也就是把values数组变成一个单一的值value)。
  3.                  
                     
    1. function(key, values) {
    2. ...
    3. return result;
    4. }
  4. values:值参数是一个数组,返回对象的类型必须与由map函数发出的值的类型相同。
  5. reduce:函数应该交换:即结果中元素的顺序不影响reduce函数的输出。
  6.                  
                     
    1. reduce( key, [ A, B ] ) == reduce( key, [ B, A ] )
              
              
  1. map操作的输出做合并的化简的操作(将key-values变成key-value,也就是把values数组变成一个单一的值value),
  2. 我们对订单的详细统计每个产品类型卖出了多少个和每种产品出现次数。
  3. 我们先通过 pnumber进行分组,然后在对 quantity相加 
  4. 相当于select pnumber,count(*),sum(quantity) from item group by pnumber
               
               
  1. var map = function() {
  2. var value={count:1, quantity:this.quantity};
  3. emit(this.pnumber,value);
  4. }
  5. var reduce=function(key,values){
  6. var reducedVal = { count: 0, quantity: 0 };
  7. for (var i = 0; i < values.length; i++) {
  8. reducedVal.count += values[i].count;
  9. reducedVal.quantity += values[i].quantity;
  10. }
  11. return reducedVal;
  12. }
  13. db.item.mapReduce( map,
  14. reduce,
  15. { out: "map_reduce_data" }
  16. ).find()
  • 输出结果:
                
                
  1. /* 1 */
  2. {
  3. "_id" : "p001",
  4. "value" : {
  5. "count" : 2.0,
  6. "quantity" : 12.0
  7. }
  8. }
  9. /* 2 */
  10. {
  11. "_id" : "p002",
  12. "value" : {
  13. "count" : 3.0,
  14. "quantity" : 8.0
  15. }
  16. }
  17. /* 3 */
  18. {
  19. "_id" : "p003",
  20. "value" : {
  21. "count" : 3.0,
  22. "quantity" : 16.0
  23. }
  24. }

参考来源: http://blog.csdn.net/congcong68/article/details/51471460
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琦彦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值