[关闭]
@a5635268 2015-09-17T11:23:06.000000Z 字数 4906 阅读 1096

【mongoDB高级篇③】综合实战(1): 分析国家地震数据

mongoDB 疑问留存


数据准备

  1. 下载国家地震数据 http://data.earthquake.cn/data/
  2. 通过navicat导入到数据库,方便和mysql语句做对比

shard分片集群配置

  1. # step 1
  2. mkdir -p ./data/shard/s0 ./data/shard/s1 #创建数据目录
  3. mkdir -p ./data/shard/log # 创建日志目录
  4. ./bin/mongod --port 27017 --dbpath /usr/local/mongodb/data/shard/s0 --fork --logpath /usr/local/mongodb/data/shard/log/s0.log # 启动Shard Server实例1
  5. ./bin/mongod --port 27018 --dbpath /usr/local/mongodb/data/shard/s1 --fork --logpath /usr/local/mongodb/data/shard/log/s1.log # 启动Shard Server实例2
  6. # step 2
  7. mkdir -p ./data/shard/config #创建数据目录
  8. ./bin/mongod --port 27027 --dbpath /usr/local/mongodb/data/shard/config --fork --logpath /usr/local/mongodb/data/shard/log/config.log #启动Config Server实例
  9. # step 3
  10. ./bin/mongos --port 4000 --configdb localhost:27027 --fork --logpath /usr/local/mongodb/data/shard/log/route.log --chunkSize=1 # 启动Route Server实例
  11. # step 4
  12. ./bin/mongo admin --port 4000 #此操作需要连接admin库
  13. > db.runCommand({ addshard:"localhost:27017" }) #添加 Shard Server 或者用 sh.addshard()命令来添加,下同;
  14. { "shardAdded" : "shard0000", "ok" : 1 }
  15. > db.runCommand({ addshard:"localhost:27018" })
  16. { "shardAdded" : "shard0001", "ok" : 1 }
  17. > db.runCommand({ enablesharding:"map" }) #设置分片存储的数据库
  18. { "ok" : 1 }
  19. > db.runCommand({ shardcollection: "map.dz", key: { id:1 }}) # 设置分片的集合名称。且必须指定Shard Key,系统会自动创建索引,然后根据这个shard Key来计算
  20. { "collectionsharded" : "map.dz", "ok" : 1 }
  21. # 手动预先分片
  22. for(var i=1;i<=30;i++) { sh.splitAt('map.dz',{id:i*1000}) }

然后通过MongoVUE把mysql中的数据导入到mongos(4000)中

数据分析实战

根据震级类型来求和

  1. /******通过group******/
  2. db.dz.group({
  3. key:{type:1},
  4. initial:{count:0},
  5. reduce: function ( curr, result ) {
  6. result.count ++;
  7. }
  8. })
  9. // Error: group command failed: { "ok" : 0, "errmsg" : "can't do command: group on sharded collection" }
  10. // group不能使用在分片上
  11. /******通过聚合管道aggregate******/
  12. db.dz.aggregate([
  13. {
  14. $group:{
  15. _id:"$type",
  16. count:{$sum:1}
  17. }
  18. }
  19. /******通过映射化简mapReduce******/
  20. var map = function(){
  21. emit(this.type,1); //把1映射到每个this.type上,然后sum就为count,还有一个技巧就是把count映射到1上,就是求总和
  22. }
  23. var reduce = function(type,count){
  24. var total = Array.sum(count);
  25. // return {type:type,count:total}; 注意,这样返回是错误的,total是一个对象??? {type:type,count:count};
  26. return total;
  27. }
  28. //或者
  29. var reduce = function(type,count){
  30. var res = 0;
  31. for (var i = 0; i < count.length;i++) {
  32. res +=count[i];
  33. }
  34. return res;
  35. }
  36. db.dz.mapReduce(map,reduce,{out:'res'});

根据日期来分组看哪一月的地震最多

  1. /*****地震每日发生次数最多的地方*****/
  2. db.dz.aggregate([
  3. { $group:{
  4. _id:{date:"$date"}, //还不知道如何通过 date.substring(0,6)来分组,先跳过,做按日来分组,当然这里的date还是字符串,如果是日期类型的话,就好处理了,这就延伸出另外一个问题,字符串如何转换为时间类型;
  5. count:{$sum:1},
  6. }
  7. },
  8. {
  9. $sort:{count:-1} // 做了个降序
  10. },
  11. {
  12. $limit:1
  13. }
  14. ]);
  15. /*****每日发生地震次数最多的10个地方,并求出最大值*****/
  16. db.dz.aggregate([
  17. { $group:{
  18. _id:{date:"$date",address:"$address"},
  19. count:{$sum:1},
  20. maxvalue:{$max:"$value"},
  21. }
  22. },
  23. {
  24. $sort:{count:-1}
  25. },
  26. {
  27. $limit:10
  28. }
  29. ]);

求每5个经纬度范围的地震次数;

  1. var map = function(){
  2. //映射到经纬度
  3. var latitude = Math.floor(this.latitude/5)*5;
  4. var longitude = Math.floor(this.longitude/5)*5; //除5下取整又乘以5,目的得到的经纬度都是5的倍数,也就是每隔5就一个数;
  5. var block = latitude+':'+longitude;
  6. emit(block,1); //总共统计每block出现地震的次数;
  7. }
  8. var reduce = function(block,value){
  9. return Array.sum(value);
  10. }
  11. db.runCommand({
  12. mapReduce:'dz',
  13. map:map,
  14. reduce:reduce,
  15. out:'res'
  16. })
  17. db.res.find().sort({value:-1});

每月发生地震次数最多的10个地方,并求出震级最大值

方法一,该方法有误,未完成,先记录

注意,本方法有一些问题我是花了很多功夫都没解决,先记录一下,如果有玩mongoDB的朋友有缘看到这篇文章,又有心的话,希望留言指正;
当然,这属于技术上的一个钻牛角尖,其实完全可以绕开的...

  1. var map = function(){
  2. var date = this.date.substring(0,6);
  3. emit(date,{count:this.address,value:this.value});//把地点和值映射到月份上
  4. }
  5. var reduce = function(date,result){
  6. /*
  7. // 此时result的结构应该如下,为每月的地址数据明细
  8. // 注意这里说的是应该,但实际上不是,这与我理解的mapReduce有误,并且我暂时还不能理解该结构最终为什么会呈现出差异,所以,我先按以下的结构,来在Reduce中做js处理
  9. "result": [
  10. {
  11. "address": "新疆阿图什",
  12. "value": 1.6
  13. },
  14. {
  15. "address": "云南澜沧",
  16. "value": 1.3
  17. },
  18. {
  19. "address": "新疆哈密",
  20. "value": 2
  21. }
  22. ]
  23. //我想要得到的结果如下:
  24. [{'四川木里':{count:2,max:5.2},'云南玉龙':{count:100,max:4.5}}]
  25. */
  26. var arr = [];
  27. for (var i = 0; i < result.length;i++) {
  28. var arrTmp = [result[i]];
  29. var address = result[i]['address'];
  30. for (var j = i+1; j < result.length; j++) {
  31. if(result[j]['address'] == address){
  32. arrTmp.push(result[j]);
  33. result.splice(j,1);
  34. j--;
  35. }
  36. };
  37. var value = []
  38. for(var a=0; a <arrTmp.length;a++){
  39. if(value.indexOf(arrTmp[a]['value']) == '-1'){
  40. value.push(arrTmp[a]['value']);
  41. }
  42. }
  43. var max = 0;
  44. for(var i=0;i<value.length;i++){
  45. max = max < value[i]?value[i]:max;
  46. }
  47. var ele = {};
  48. ele[address] = {count:arrTmp.length,max:max};
  49. arr.push(ele);
  50. }
  51. return {result:arr};
  52. }
  53. db.runCommand({
  54. mapReduce:'dz',
  55. map:map,
  56. reduce:reduce,
  57. finalize:finalize, // 由于Reduce返回的结构是有误的,所以finalize还没办法处理,先留空;
  58. out:'res'
  59. })

方法二

本方法也有一个让我百思不得其解的问题,在注释部分有说明;

  1. var map = function(){
  2. var date = this.date.substring(0,6);
  3. var map = date+'_'+this.address;
  4. emit(map,{count:1,value:this.value});
  5. }
  6. var reduce = function(date,result){
  7. var count = 0;
  8. for(var i=0;i<result.length;i++){
  9. count += result[i]['count']; // result[i]['count']的值都是1
  10. }
  11. //var count = result.length; // 一开始我的count值是这样写的,但是结果是错误的与mysql算出来的不符合,改成上面的才正确,这里也让我很郁闷,result[i]['count']的值都是1,result.length是其result元素的总合,按道理这个count和上面的count是一样的,但事实证明,我又错了,居然不一样....又是一个理解不了的问题;
  12. var value = [];
  13. for(var i=0;i<result.length;i++){
  14. value.push(result[i].value);
  15. }
  16. var max=0;
  17. for(var i=0;i<value.length;i++){
  18. max = max < value[i]?value[i]:max;
  19. }
  20. return {count:count,max:max};
  21. }
  22. db.runCommand({
  23. mapReduce:'dz',
  24. map:map,
  25. reduce:reduce,
  26. out:'res'
  27. })
  28. db.res.find().sort({'value.count':-1}).limit(10); //在输出集合中再进行筛选
  29. // 但是,第一多的数据和mysql算下来的不同,其后9名都是相同的

mongoDB系列文章到此先告一段落,后续再添加 【mongoDB高级篇】mongoDB在LBS中的应用; 2015-9-17

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注