Mongodb聚合(三)——mapreduce

Mongodb聚合(三)

3. MapReduce
Mapreduce很是強大與靈活,Mongodb使用javascript做爲查詢語言,能夠表示任意複雜的邏輯。
Mapreduce很是慢,不該該用在實際的數據分析中。
Mapreduce能夠在多臺服務器之間並行執行,能夠將一個問題拆分爲多個小問題,以後將各個小問題發送到不一樣的機器上,每臺機器只負責完成一部分工做,全部的機器完成時,將這些零碎的解決方案合併爲一個完整的解決方案。
最開始的是映射(map),將操做映射到集合中的各個文檔,而後是中間環節,成爲洗牌(shuffle),按照鍵分組,將產生的鍵值組成列表放在對應的鍵中。化簡(reduce)則是把列表中的值化簡爲一個單值。

3.1 找出集合中的全部鍵
MongoDB假設你的模式是動態的,因此並不會跟蹤記錄每一個文檔的鍵。一般找到集合中全部文檔的全部鍵的最好方式就是MapReduce。
在映射環節,map函數使用特別的emit函數返回要處理的值。emit會給MapReduce一個鍵和一個值。
這裏用emit將文檔某個鍵的計數返回。this就是當前映射文檔的引用:
map = function() {
    emit(this.country, {count : 1});
}
reduce接受兩個參數,一個是key,就是emit返回的第一個值,還有一個數組,由一個或多個鍵對應的{count : 1}文檔組成。
reduce = function(key, value) {
    var result = {count : 0};
    for (var i = 0; i < value.length; i++) {
        result.count += value[i].count;
    }
    return result;
}
示例表數據:
{ "_id" : 38, "country" : "japan", "money" : 724 }
{ "_id" : 39, "country" : "germany", "money" : 520 }
{ "_id" : 40, "country" : "india", "money" : 934 }
{ "_id" : 41, "country" : "china", "money" : 721 }
{ "_id" : 42, "country" : "germany", "money" : 156 }
{ "_id" : 43, "country" : "canada", "money" : 950 }
{ "_id" : 44, "country" : "india", "money" : 406 }
{ "_id" : 45, "country" : "japan", "money" : 776 }
{ "_id" : 46, "country" : "canada", "money" : 468 }
{ "_id" : 47, "country" : "germany", "money" : 262 }
{ "_id" : 48, "country" : "germany", "money" : 126 }
{ "_id" : 49, "country" : "japan", "money" : 86 }
{ "_id" : 50, "country" : "canada", "money" : 870 }
{ "_id" : 51, "country" : "india", "money" : 98 }
{ "_id" : 52, "country" : "india", "money" : 673 }
{ "_id" : 53, "country" : "japan", "money" : 487 }
{ "_id" : 54, "country" : "india", "money" : 681 }
{ "_id" : 55, "country" : "canada", "money" : 491 }
{ "_id" : 56, "country" : "japan", "money" : 98 }
{ "_id" : 57, "country" : "china", "money" : 172 }
Type "it" for more
運行結果:
db.foo.mapReduce(map, reduce, {out : "collection"})
{
        "result" : "collcetion",
        "timeMillis" : 83,
        "counts" : {
                "input" : 99,
                "emit" : 99,
                "reduce" : 5,
                "output" : 5
        },
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1399168165, 15),
                "electionId" : ObjectId("535a2ce15918f42de9ab1427")
        },
}
result:存放的集合名
timeMillis:操做花費的時間,單位是毫秒
input:傳入文檔數目
emit:此函數被調用的次數
reduce:此函數被調用的次數
output:最後返回文檔的個數
查看下collection結果內容:
 db.collection.find();
{ "_id" : "canada", "value" : { "count" : 19 } }
{ "_id" : "china", "value" : { "count" : 15 } }
{ "_id" : "germany", "value" : { "count" : 25 } }
{ "_id" : "india", "value" : { "count" : 20 } }
{ "_id" : "japan", "value" : { "count" : 20 } }

3.2 MapRecude其餘的鍵
"finalize" : function
能夠將reduce的結果發送給這個鍵,這是整個處理過程的最後一步。
"keeptemp自動爲true。" : boolean
若是爲true,則在鏈接關閉後結果保存,不然不保存。
"out" : string
輸出集合的名稱,若是設置,keeptemp自動爲true。
"query" : document
在發往map前,先用指定條件過濾文檔。
"sort" : document
在發往map前,先進行排序。
"limit" : integer
發往map函數的文檔數量上限。
"scope" : document
能夠在javascripts代碼中使用的變量。
"verbose" : boolean
是否記錄詳細的服務器日誌。
3.2.1 finalize函數
可使用finalize函數做爲參數,會在最後一個reduce輸出結果後執行,而後將結果保存在臨時集合裏。
3.2.2 保存結果集合
默認狀況下,執行mapreduce時建立一個臨時集合,集合名稱爲mr.stuff.ts.id,即mapreduce.集合名.時間戳.數據庫做業ID。MongoDB會在調用的鏈接關閉時自動銷燬這個集合。
3.2.3 對子文檔執行mapreduce
每一個傳遞給map的文檔都須要先反序列化,從BSON對象轉換爲js對象,這個過程很是耗時,能夠先對文檔過濾來提升map速度,能夠經過"query","limit"和"sort"等對文檔進行過濾。
"query"的值是一個查詢文檔。
"limit","sort"配合能夠發揮很大的做用。
"query","limit"和"sort"能夠隨意組合使用。
3.2.4 做用域
做用域鍵"scope",能夠用變量名:值這樣普通的文檔來設置該選項,
3.2.5 獲取更多的輸出
設置verbose爲true,能夠將mapreduce過程更多的信息輸出到服務器日誌上。

4 聚合命名
count和distinct操做能夠簡化爲普通命令,不須要使用聚合框架。
4.1 count
count返回集合中的文檔數量:
db.foo.count() =>
99
能夠傳入一個查詢文檔:
db.foo.count({country : "china"}) =>
15
增長查詢條件會使count變慢。
4.2 distinct
distinct用來找出給定鍵的全部不一樣值。使用時必須指定集合和鍵。
db.runCommand({ "distinct" : "foo", "key" : "country"}) =>
{
        "values" : [
                "japan",
                "germany",
                "india",
                "china",
                "canada"
        ],
        "stats" : {
                "n" : 99,
                "nscanned" : 99,
                "nscannedObjects" : 99,
                "timems" : 22,
                "cursor" : "BasicCursor"
        },
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1399171995, 15),
                "electionId" : ObjectId("535a2ce15918f42de9ab1427")
        }
}
4.3 group
使用group能夠進行更爲複雜的聚合。先選定分組所依據的鍵,而後根據選定鍵的不一樣值分爲若干組,而後對每個分組進行聚合,獲得結果文檔。
插入示例數據:
var name = ["Caoqing", "Spider-man", "Garfield"]
for (var i = 0; i < 10000; i++) {
    iname = name[Math.floor(Math.random() * name.length)];
    date = new Date().getTime();
    number = Math.floor(100 * Math.random());
    db.coll.insert({_id : i, name : iname, time : date, age : number});
}
生成的列表中包含最新的時間和最新的時間對應的年紀。
能夠安裝name進行分組,而後取出每一個分組中date最新的文檔,將其加入結果集。
db.runCommand({"group" : {
    "ns" : "coll",
    "key" : {"name" : true},
    "initial" : {"time" : 0},
    "$reduce" : function(doc, prev) {
        if (doc.time > prev.time) {
            prev.age = doc.age;
            prev.time = doc.time;
        }
    }
}})
"ns" : "coll"
指定進行分組的集合。
"key" : {"name" : true}
指定分組依據的鍵。
"initial" : {"time" : 0}
初始化time值,做爲初始Wednesday傳遞給後續過程。每組成員都會使用這個累加器。
結果:
"$reduce" : function(doc, prev) {...}
{
        "retval" : [
                {
                        "name" : "Spider-man",
                        "time" : 1399179398567,
                        "age" : 55
                },
                {
                        "name" : "Garfield",
                        "time" : 1399179398565,
                        "age" : 85
                },
                {
                        "name" : "Caoqing",
                        "time" : 1399179398566,
                        "age" : 86
                }
        ],
        "count" : 10000,
        "keys" : 3,
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1399179362, 1),
                "electionId" : ObjectId("535a2ce15918f42de9ab1427")
        }
}
若是有文檔不存在指定分組的鍵,這些文檔會單獨分爲一組,缺失的鍵會使用name:null這樣的形式。以下:
db.coll.insert({age : 5, time : new Date().getTime()})
返回結果:
        ...
        {
            "name" : null,
            "time" : 1399180685288,
            "age" : 5
        }
        "count" : 10001,
        "keys" : 4,
        ...
爲了排除不包含指定用於分組的鍵的文檔,能夠在"condition"中加入"name":{"$exists" : true}。
db.runCommand({"group" : {
    "ns" : "coll",
    "key" : {"name" : true},
    "initial" : {"time" : 0},
    "$reduce" : function(doc, prev) {
        if (doc.time > prev.time) {
            prev.age = doc.age;
            prev.time = doc.time;
        }
    },
    "condition" : {"name" : {"$exists" : true}}
}})
4.3.1 使用完成器
完成器(finalizer)用於精簡從數據庫傳到用戶的數據,由於group命令的輸出結果須要可以經過單次數據庫響應返回給用戶。
4.3.2 將函數做爲鍵使用
分組條件能夠很是複雜,不是單個鍵,例如分組時按照類別分組dog和DOG是兩個徹底不一樣的組,爲了消除大小寫差別,能夠定義一個函數決定文檔分組所依據的鍵。
定義分組函數須要用到"$keyf"鍵,
db.foo.group({
    "ns" : "foo",
    "$keyf" : function(x) { return x.category.toLowerCase(); };
    "initial" : ...,
    ......
})
相關文章
相關標籤/搜索