學習MongoDB 十: MongoDB聚合(Map-Reduce)(二)

         

       MapReduce要實現兩個函數:Map和Reduce。Map函數調用emit(key,value)遍歷一個或多個集合中所有的記錄,進行分組(group by),然後將key與value傳給Reduce函數進行處理,輸出結果。

   
            (1)MapReduce使用自定義JavaScript函數執行map和reduce操作,所以是基於js引擎,單線程執行,效率不高,比Aggregation複雜,適合用做後臺統計等。
            (2)MapReduce支持分片操作,可以進行拆分,分發到不同的機器上執行(多服務器並行做數據集合處理),然後再將不同的機器處理的結果彙集起 來,輸出結果,。
(3)MapReduce能執行單一聚合的所有操作count、distinct、group,但group 在當數據量非常大的時候,處理能力就不太好,先篩選再分組,不支持 分片,對數據量有所限制,效率不高。

 
  MapReduce語法】
   
          先對MapReduce語法認識一下,各個參數有什麼作用,接下去的操作理解起來會比較容易。
          
<span style="font-size:18px;">  db.collection.mapReduce(
                         <map>,
                         <reduce>,
                         {
                           out: <collection>,
                           query: <document>,
                           sort: <document>,
                           limit: <number>,
                           finalize: <function>,
                           scope: <document>,
                           jsMode: <boolean>,
                           verbose: <boolean>,
                           bypassDocumentValidation: <boolean>
                         }
                       )</span>

   參數說明:
    map:是JavaScript 函數,負責將每一個輸入文檔轉換爲零或多個文檔,通過key進行分組,生成鍵值對序列,作爲 reduce 函數參數。
    reduce:是JavaScript 函數,對map操作的輸出做合併的化簡的操作(將key-values變成key-value,也就是把values數組變成一個單一的值value)。
    out:reduce執行完,存放的集合,如果不指定集合,則使用默認的臨時集合,在MapReduce的連接關閉後自動就被刪除了。
out: { <action>: <collectionName>
[, db: <dbName>]
[, sharded: <boolean> ]
[, nonAtomic: <boolean> ] }
    query:過濾的條件,對符合條件的文檔執行map函數。(query。limit,sort可以隨意組合)。
    sort :對文檔進行排序,sort和limit結合的sort排序參數(也是在發往map函數前給文檔排序),可以優化分組機制。
    limit :發往map函數的文檔數量的上限(要是沒有limit,單獨使用sort的用處不大)。
  finalize:可以對reduce輸出結果再一次修改,跟group的finalize一樣,不過MapReduce沒有group的4MB文檔的輸出限制。
  scope:向map、reduce、finalize導入外部變量。
   verbose:是否包括結果信息中的時間信息,默認爲fasle。
     "timing" : {
                "mapTime" : 0,
                "emitLoop" : 2,
                "reduceTime" : 0,
                "mode" : "mixed",
                "total" : 0
        }


   【map函數】

        map是JavaScript 函數,負責將每一個輸入文檔轉換爲零或多個文檔,通過key進行分組,生成鍵值對序列,作爲 reduce 函數參數。
  function() {
            emit(key, value);
        }
  

  key對文檔進行分組,value是要統計的數據,value可以是JSON對象(emit只能容納MongoDB的最大BSON文件大小的一半)。我們對訂單的詳細統計每個產品類型賣出了多少個。我們先通過 pnumber進行分組,然後在對 quantity相加 相當於select pnumber,sum(quantity) from item   group by pnumber


<span style="font-size:18px;">db.item.insert( [
  {
   "quantity" : 2,
   "price" : 5.0,
   "pnumber" : "p003"
  },{
   "quantity" : 2,
   "price" : 8.0,
   "pnumber" : "p002"
  },{
   "quantity" : 1,
   "price" : 4.0,
   "pnumber" : "p002"
  },{
   "quantity" : 2,
   "price" : 4.0,
   "pnumber" : "p001"
  },{
   "quantity" : 4,
   "price" : 10.0,
   "pnumber" : "p003"
  },{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p001"
  },{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p003"
  },{
   "quantity" : 5,
   "price" : 10.0,
   "pnumber" : "p002"
  }
])


> var map = function() { emit(this.pnumber,this.quantity)}
> var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
> db.item.mapReduce( map,
                     reduce,
                    { out: "map_reduce_data" }  
				   )
 > db.map_reduce_data.find()
{ "_id" : "p001", "value" : { "pumber" : "p001", "quantity" : 12 } }
{ "_id" : "p002", "value" : { "pumber" : "p002", "quantity" : 8 } }
{ "_id" : "p003", "value" : { "pumber" : "p003", "quantity" : 20 } }</span>


【query過濾的條件】


   對符合條件的文檔將會執行map函數。(query。limit,sort可以隨意組合), 我們對訂單的詳細的每次每種產品賣出的數量要大於5的並統計每個產品類型賣出了多少個。我們先通過 pnumber進行分組,然後在對 quantity相加 相當於select pnumber,sum(quantity) from item where quantity>5   group by pnumber


<span style="font-size:18px;">> var map = function() { emit(this.pnumber,this.quantity)}
> var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
> db.item.mapReduce( map,                      
                       reduce,                  
					   { query:{'quantity':{$gt:5}},
					      out: "map_reduce_data" }      )
{
        "result" : "map_reduce_data",
        "timeMillis" : 5,
        "counts" : {
                "input" : 2,
                "emit" : 2,
                "reduce" : 0,
                "output" : 2
        },
        "ok" : 1
}
> db.map_reduce_data.find()
{ "_id" : "p001", "value" : 10 }
{ "_id" : "p003", "value" : 10 }</span>


 【value是JSON對象】
  
     value可以是JSON格式,我們對訂單的詳細統計每個產品類型出現次數。我們先通過 pnumber進行分組,然後在對 quantity相加 相當於select pnumber,count(*) from item   group by pnumber。
  
<span style="font-size:18px;">  >var map = function() {emit(this.pnumber,{count:1});} 
  > var reduce=function(key,values){
    var count=0;   
	values.forEach(function(val){ count+=val.count;});  
	return {'pumber':key,"count":count};
  }
> db.item.mapReduce( map,
                     reduce,
                    { out: "map_reduce_data" }  
				   )
{
        "result" : "map_reduce_data",
        "timeMillis" : 6,
        "counts" : {
                "input" : 10,
                "emit" : 10,
                "reduce" : 3,
                "output" : 3
        },
        "ok" : 1
}
> db.map_reduce_data.find()
{ "_id" : "p001", "value" : { "pumber" : "p001", "count" : 2 } }
{ "_id" : "p002", "value" : { "pumber" : "p002", "count" : 3 } }
{ "_id" : "p003", "value" : { "pumber" : "p003", "count" : 5 } }</span>

【emit多次的循環】

    可以對emit多次的循環,可以根據輸入文檔的項目字段中的元素的數量(鍵,值)多次調用:
  function() {
        this.items.forEach(function(item){ emit(key, value); });
     }

 

  我們對統計訂單中對應的產品銷售了多少個,我們先通過 pnumber進行分組,然後在對 quantity相加。

 
<span style="font-size:18px;">	 
	 
db.orders.insert( [
{
 "onumber" : "001",
 "item" : [{
   "quantity" : 2,
   "price" : 5.0,
   "pnumber" : "p003"
  },{
   "quantity" : 2,
   "price" : 8.0,
   "pnumber" : "p002"
  }]
},{
 "onumber" : "002",
 "item" : [{
   "quantity" : 1,
   "price" : 4.0,
   "pnumber" : "p002"
  },{
   "quantity" : 2,
   "price" : 4.0,
   "pnumber" : "p001"
  },{
   "quantity" : 4,
   "price" : 10.0,
   "pnumber" : "p003"
  }]
},{
 "onumber" : "003",
 "item" : [{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p001"
  },{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p003"
  }]
},{
 "onumber" : "004",
 "item" : [{
   "quantity" : 5,
   "price" : 10.0,
   "pnumber" : "p002"
  }]
}
])	

> var map = function() { this.item.forEach(function(it){ emit(it.pnumber,it.quantity); })}
> var reduce=function(key,values){return {'pumber':key,'quantity':Array.sum(values)}}
> db.orders.mapReduce( map,                      
                       reduce,                  
					   { out: "map_reduce_data" }      )
{
        "result" : "map_reduce_data",
        "timeMillis" : 51,
        "counts" : {
                "input" : 4,
                "emit" : 8,
                "reduce" : 3,
                "output" : 3
        },
        "ok" : 1
}
> db.map_reduce_data.find()
{ "_id" : "p001", "value" : { "pumber" : "p001", "quantity" : 12 } }
{ "_id" : "p002", "value" : { "pumber" : "p002", "quantity" : 8 } }
{ "_id" : "p003", "value" : { "pumber" : "p003", "quantity" : 16 } }</span>

> var map = function() { this.item.forEach(function(it){ emit(it.pnumber,it.quantity); })}
也可以這樣寫
> var map = function() { for(var i=0;i<this.item.length;i++){ var it=item[i]; emit(it.pnumber,it.quantity); }}

【reduce函數

  reduce是JavaScript 函數,對map操作的輸出做合併的化簡的操作(將key-values變成key-value,也就是把values數組變成一個單一的值value)。  

    function(key, values) {
       ...
       return result;
    }


  values:值參數是一個數組,返回對象的類型必須與由map函數發出的值的類型相同。
  reduce函數應該交換:即如果中元素的順序不影響reduce函數的輸出。
            reduce( key, [ A, B ] ) == reduce( key, [ B, A ] ) 

    對map操作的輸出做合併的化簡的操作(將key-values變成key-value,也就是把values數組變成一個單一的值value),我們對訂單的詳細統計每個產品類型賣出了多少個和每種產品出現次數。我們先通過 pnumber進行分組,然後在對 quantity相加 相當於select pnumber,count(*),sum(quantity) from item   group by pnumber
  
<span style="font-size:18px;">>var map = function() { 
     var value={count:1, quantity:this.quantity};
    emit(this.pnumber,value);
  } 
>var reduce=function(key,values){ 
                     var reducedVal = { count: 0, quantity: 0 };
                     for (var i = 0; i < values.length; i++) {
                         reducedVal.count += values[i].count;
                         reducedVal.quantity += values[i].quantity;
                     }
                     return reducedVal;
			}


{
        "result" : "map_reduce_data",
        "timeMillis" : 7,
        "counts" : {
                "input" : 10,
                "emit" : 10,
                "reduce" : 3,
                "output" : 3
        },
        "ok" : 1
}
> db.map_reduce_data.find()
{ "_id" : "p001", "value" : { "count" : 2, "quantity" : 12 } }
{ "_id" : "p002", "value" : { "count" : 3, "quantity" : 8 } }
{ "_id" : "p003", "value" : { "count" : 5, "quantity" : 20 } }</span>

【MapReduce 執行結果信息】

  我們執行MapReduce輸出結果時,有打印MapReduce信息
<span style="font-size:18px;"> 
{
        "result" : "map_reduce_data",
        "timeMillis" : 7,
        "counts" : {
                "input" : 10,
                "emit" : 10,
                "reduce" : 3,
                "output" : 3
        },
        "ok" : 1
}
</span>

result:reduce執行完,存放的集合,如果不指定集合,則使用默認的臨時集合,在MapReduce的連接關閉後自動就被刪除了。我們這邊有指定集合的名稱map_reduce_data。
timeMillis:執行MapReduce所花費的時間(毫秒)。
input:滿足條件被髮送到map函數的文檔個數。
emit:在map函數中emit被調用的次數,也就是所有集合中的數據總量。
ouput:輸出到集合中的結果文檔個數。
ok:是否成功,成功爲1。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章