## 數據準備
1. 下載國家地震數據 http://data.earthquake.cn/data/
2. 通過navicat導入到數據庫,方便和mysql語句做對比
## shard分片集群配置
```
# step 1
mkdir -p ./data/shard/s0 ./data/shard/s1 #創建數據目錄
mkdir -p ./data/shard/log # 創建日志目錄
./bin/mongod --port 27017 --dbpath /usr/local/mongodb/data/shard/s0 --fork --logpath /usr/local/mongodb/data/shard/log/s0.log # 啟動Shard Server實例1
./bin/mongod --port 27018 --dbpath /usr/local/mongodb/data/shard/s1 --fork --logpath /usr/local/mongodb/data/shard/log/s1.log # 啟動Shard Server實例2
# step 2
mkdir -p ./data/shard/config #創建數據目錄
./bin/mongod --port 27027 --dbpath /usr/local/mongodb/data/shard/config --fork --logpath /usr/local/mongodb/data/shard/log/config.log #啟動Config Server實例
# step 3
./bin/mongos --port 4000 --configdb localhost:27027 --fork --logpath /usr/local/mongodb/data/shard/log/route.log --chunkSize=1 # 啟動Route Server實例
# step 4
./bin/mongo admin --port 4000 #此操作需要連接admin庫
> db.runCommand({ addshard:"localhost:27017" }) #添加 Shard Server 或者用 sh.addshard()命令來添加,下同;
{ "shardAdded" : "shard0000", "ok" : 1 }
> db.runCommand({ addshard:"localhost:27018" })
{ "shardAdded" : "shard0001", "ok" : 1 }
> db.runCommand({ enablesharding:"map" }) #設置分片存儲的數據庫
{ "ok" : 1 }
> db.runCommand({ shardcollection: "map.dz", key: { id:1 }}) # 設置分片的集合名稱。且必須指定Shard Key,系統會自動創建索引,然后根據這個shard Key來計算
{ "collectionsharded" : "map.dz", "ok" : 1 }
# 手動預先分片
for(var i=1;i<=30;i++) { sh.splitAt('map.dz',{id:i*1000}) }
```
> 然后通過MongoVUE把mysql中的數據導入到mongos(4000)中
## 數據分析實戰
### 根據震級類型來求和
```javascript
/******通過group******/
db.dz.group({
key:{type:1},
initial:{count:0},
reduce: function ( curr, result ) {
result.count ++;
}
})
// Error: group command failed: { "ok" : 0, "errmsg" : "can't do command: group on sharded collection" }
// group不能使用在分片上
/******通過聚合管道aggregate******/
db.dz.aggregate([
{
$group:{
_id:"$type",
count:{$sum:1}
}
}
/******通過映射化簡mapReduce******/
var map = function(){
emit(this.type,1); //把1映射到每個this.type上,然后sum就為count,還有一個技巧就是把count映射到1上,就是求總和
}
var reduce = function(type,count){
var total = Array.sum(count);
// return {type:type,count:total}; 注意,這樣返回是錯誤的,total是一個對象??? {type:type,count:count};
return total;
}
//或者
var reduce = function(type,count){
var res = 0;
for (var i = 0; i < count.length;i++) {
res +=count[i];
}
return res;
}
db.dz.mapReduce(map,reduce,{out:'res'});
```
### 根據日期來分組看哪一月的地震最多
```javascript
/*****地震每日發生次數最多的地方*****/
db.dz.aggregate([
{ $group:{
_id:{date:"$date"}, //還不知道如何通過 date.substring(0,6)來分組,先跳過,做按日來分組,當然這里的date還是字符串,如果是日期類型的話,就好處理了,這就延伸出另外一個問題,字符串如何轉換為時間類型;
count:{$sum:1},
}
},
{
$sort:{count:-1} // 做了個降序
},
{
$limit:1
}
]);
/*****每日發生地震次數最多的10個地方,并求出最大值*****/
db.dz.aggregate([
{ $group:{
_id:{date:"$date",address:"$address"},
count:{$sum:1},
maxvalue:{$max:"$value"},
}
},
{
$sort:{count:-1}
},
{
$limit:10
}
]);
```
### 求每5個經緯度范圍的地震次數;
```javascript
var map = function(){
//映射到經緯度
var latitude = Math.floor(this.latitude/5)*5;
var longitude = Math.floor(this.longitude/5)*5; //除5下取整又乘以5,目的得到的經緯度都是5的倍數,也就是每隔5就一個數;
var block = latitude+':'+longitude;
emit(block,1); //總共統計每block出現地震的次數;
}
var reduce = function(block,value){
return Array.sum(value);
}
db.runCommand({
mapReduce:'dz',
map:map,
reduce:reduce,
out:'res'
})
db.res.find().sort({value:-1});
```
### 每月發生地震次數最多的10個地方,并求出震級最大值
#### **方法一,該方法有誤,未完成,先記錄**
> 注意,本方法有一些問題我是花了很多功夫都沒解決,先記錄一下,如果有玩mongoDB的朋友有緣看到這篇文章,又有心的話,希望留言指正;
> 當然,這屬于技術上的一個鉆牛角尖,其實完全可以繞開的...
```javascript
var map = function(){
var date = this.date.substring(0,6);
emit(date,{count:this.address,value:this.value});//把地點和值映射到月份上
}
var reduce = function(date,result){
/*
// 此時result的結構應該如下,為每月的地址數據明細
// 注意這里說的是應該,但實際上不是,這與我理解的mapReduce有誤,并且我暫時還不能理解該結構最終為什么會呈現出差異,所以,我先按以下的結構,來在Reduce中做js處理
"result": [
{
"address": "新疆阿圖什",
"value": 1.6
},
{
"address": "云南瀾滄",
"value": 1.3
},
{
"address": "新疆哈密",
"value": 2
}
]
//我想要得到的結果如下:
[{'四川木里':{count:2,max:5.2},'云南玉龍':{count:100,max:4.5}}]
*/
var arr = [];
for (var i = 0; i < result.length;i++) {
var arrTmp = [result[i]];
var address = result[i]['address'];
for (var j = i+1; j < result.length; j++) {
if(result[j]['address'] == address){
arrTmp.push(result[j]);
result.splice(j,1);
j--;
}
};
var value = []
for(var a=0; a <arrTmp.length;a++){
if(value.indexOf(arrTmp[a]['value']) == '-1'){
value.push(arrTmp[a]['value']);
}
}
var max = 0;
for(var i=0;i<value.length;i++){
max = max < value[i]?value[i]:max;
}
var ele = {};
ele[address] = {count:arrTmp.length,max:max};
arr.push(ele);
}
return {result:arr};
}
db.runCommand({
mapReduce:'dz',
map:map,
reduce:reduce,
finalize:finalize, // 由于Reduce返回的結構是有誤的,所以finalize還沒辦法處理,先留空;
out:'res'
})
```
#### **方法二**
> 本方法也有一個讓我百思不得其解的問題,在注釋部分有說明;
```javascript
var map = function(){
var date = this.date.substring(0,6);
var map = date+'_'+this.address;
emit(map,{count:1,value:this.value});
}
var reduce = function(date,result){
var count = 0;
for(var i=0;i<result.length;i++){
count += result[i]['count']; // result[i]['count']的值都是1
}
//var count = result.length; // 一開始我的count值是這樣寫的,但是結果是錯誤的與mysql算出來的不符合,改成上面的才正確,這里也讓我很郁悶,result[i]['count']的值都是1,result.length是其result元素的總合,按道理這個count和上面的count是一樣的,但事實證明,我又錯了,居然不一樣....又是一個理解不了的問題;
var value = [];
for(var i=0;i<result.length;i++){
value.push(result[i].value);
}
var max=0;
for(var i=0;i<value.length;i++){
max = max < value[i]?value[i]:max;
}
return {count:count,max:max};
}
db.runCommand({
mapReduce:'dz',
map:map,
reduce:reduce,
out:'res'
})
db.res.find().sort({'value.count':-1}).limit(10); //在輸出集合中再進行篩選
// 但是,第一多的數據和mysql算下來的不同,其后9名都是相同的
```
> mongoDB系列文章到此先告一段落,后續再添加 【mongoDB高級篇】mongoDB在LBS中的應用; 2015-9-17