2016-03-03 77 views
1

我對Mongo聚合相當陌生,而且我目前的Mongo-fu已經達到了極限。MongoDB - 進一步轉換聚合列表

舉個例子,假設與下面的文檔結構「加息」記錄的集合:

{ 
    hiker_id: 123, 
    trail: "Dusty Peak" 
} 

是我使用

db.hikes.aggregate([{$group: {_id: "$hiker_id", trails: {$addToSet: "$trail"}}}]) 

我會得到這樣的:

{ 
    _id: 123, 
    trails: ["Dusty Peak", "Windy Falls", "Mushroom Alley", ... 
} 

但是,如果一個徒步旅行者徒步了同樣的線索多次,我們會看到在重複名單,所以我真正喜歡的是:

{ 
    _id: 123, 
    trails: { "Dusty Peak": 2, 
      "Windy Falls": 1, 
      "Mushroom Alley": 4, 
      ... 
      } 
} 

的登山者已經多少次上調各路徑的總結。我會怎麼做aggregate

- 或 -

這是不是應該改爲在完成完成步驟的的map-reduce? Mongo自己的文檔表示,MR的性能較差,而且性能對於我正在進行的工作至關重要。

+0

我會建議在這裏分離您的問題。我會用$ addToSet進行更新,然後執行聚合管道並計算引用。 – jmugz3

+0

我完全同意,這對我來說在語言層面很容易,但在Mongo層面上,我不確定語義。 –

+0

如果您先執行$ addtoSet,那麼您可以進行彙總並使用$ sum來計算字段。 – jmugz3

回答

4

$addToSet運營商實際上只是另一種$group,但只包含數組項結果。因此,要計算這些鍵的出現次數,只需對它們進行「分組」即可。第二$group可以把它們放到一個數組:

db.hikes.aggregate([ 
    // Group on distinct trail per hiker 
    { "$group": { 
     "_id": { 
      "hiker": "$hiker_id", 
      "trail": "$trail" 
     }, 
     "count": { "$sum": 1 } 
    }}, 

    // Now roll-up per hiker and push to array 
    { "$group": { 
     "_id": "$_id.hiker", 
     "trails": { 
      "$push": { "name": "$_id.trail", "count": "$count" } 
     } 
    }} 
]) 

這給你的結果,如:

{ 
    "_id": 123, 
    "trails": [ 
     { "name": "Dusty Peak", "count": 2 }, 
     { "name": "Windy Falls", "count": 1 }, 
     { "name": "Mushroom Alley", "count": 4 } 
    ] 
} 

如果你仔細想想那時候真的所有你需要在第一$group實際取得的成果管道階段,儘管每個徒步旅行者每個線索在一個文件中。所有第二個$group正在做(實際上很快)只是通過將其餘信息添加到數組中來「捲起」每個遠足者的結果。

這是不一樣的你有什麼建議,但它的聚合框架做什麼。它不會以任何方式將「數據」轉換爲「鍵」。恕我直言,這是一件好事,因爲我不認爲代表數據點的「指定密鑰」是一個好主意。上面是乾淨的,很容易迭代爲一個自然數組。當然,所有需要的數據都在那裏。

如果真有你的心臟上tranforming到鍵設置,那麼上面仍然適用,最好是剛做的穿越 - 客戶端:

db.hikes.aggregate([ 
    // Group on distinct trail per hiker 
    { "$group": { 
     "_id": { 
      "hiker": "$hiker_id", 
      "trail": "$trail" 
     }, 
     "count": { "$sum": 1 } 
    }}, 

    // Now roll-up per hiker and push to array 
    { "$group": { 
     "_id": "$_id.hiker", 
     "trails": { 
      "$push": { "name": "$_id.trail", "count": "$count" } 
     } 
    }} 
]).forEach(function(doc) { 
    var newTrails = {}; 
    doc.trails.forEach(function(trail) { 
     newTrails[trail.name] = trail.count; 
    }); 
    doc.trails = newTrails; 
    printjson(doc); 
}) 

或者基本上是類似的迭代器模式在任何語言實現您使用。


爲了記錄在案,這樣做的MapReduce的方法是:

db.hikes.mapReduce(
    function() { 
     var data = {}; 
     data[this.trail] = 1; 
     emit(this.hiker_id,data); 
    }, 
    function(key,values) { 
     var result = {}; 
     values.forEach(function(value) { 
      Object.keys(value).forEach(function(key) { 
       if (!result.hasOwnProperty(key)) 
        result[key] = 0; 
       result[key] += value[key]; 
      }) 
     }); 
     return result; 
    }, 
    { "out": { "inline": 1 } } 
) 

這在我的腦海裏是一種愚蠢的,因爲額外的「分組」依靠迭代對象鍵。其結果也有它自己的MapReduce的怪癖:

{ 
    "_id": 123, 
    "value": { 
     "Dusty Peak": 2, 
     "Mushroom Alley": 4, 
     "Windy Falls": 1 
    } 
} 

還以爲是服務器上的所有完成的,它不是沒有它的成本,而不是隻在JavaScript interpretaion。 mapReduce過程通常會多次調用reducer函數,這意味着reducer的輸出實際上可能會在輸入(關鍵設計點)時結束。從這個角度來看,它意味着在連續傳遞時結果對象將「增長」,這意味着在迭代和測試密鑰的存在時會產生更多開銷。

備用匯總框架流程以更自然的方式處理此問題,並使用$group數據收集中的高效算法進行處理。

+0

現在我想到了,作爲對象列表的「trail」效果更好。謝謝! –

+0

@fosskers感謝信任投票,但是如果您[接受了答案],也將不勝感激。(http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work ) –

0

您可以用骨料使用複合指數(由徒步旅行者和線索有效分組,然後做第二個$組對hiker_id,這個時候推路徑名稱和計數做到這一點,例如:

db.hikes.aggregate([ 
    {$group:{_id:{"hiker_id":"$hiker_id", "trail":"$trail"},count:{$sum:1}}}, 
    {$group:{_id:"$_id.hiker_id", trails:{$push:{"trail":"$_id.trail","count":"$count"}}}} 
]) 

所以有compount _id這是hiker_id和線索的組合的第一部分組,然後第二部分重新組合只是hiker_id,並推動路徑名稱和數量

所以給出的集合是這樣的:。

> db.hikes.find() 
{ "_id" : ObjectId("56d8b6bb3e30c2d1435acf96"), "hiker_id" : 123, "trail" : "Dusty Peak" } 
{ "_id" : ObjectId("56d8b6d83e30c2d1435acf97"), "hiker_id" : 123, "trail" : "Foo" } 
{ "_id" : ObjectId("56d8b6da3e30c2d1435acf98"), "hiker_id" : 123, "trail" : "Dusty Peak" } 
{ "_id" : ObjectId("56d8b6de3e30c2d1435acf99"), "hiker_id" : 123, "trail" : "Foo" } 
{ "_id" : ObjectId("56d8b6e63e30c2d1435acf9a"), "hiker_id" : 123, "trail" : "Bar" } 

你會得到這樣的結果:

{ 
    "_id" : 123, 
    "trails" : [ 
     { 
      "trail" : "Bar", 
      "count" : 1 
     }, 
     { 
      "trail" : "Foo", 
      "count" : 2 
     }, 
     { 
      "trail" : "Dusty Peak", 
      "count" : 2 
     } 
    ] 
}