2015-11-14 60 views
0

Mongodb網站https://docs.mongodb.org/ecosystem/use-cases/pre-aggregated-reports/提供了一個文檔示例,可以在每日,每月級快速查找。通過搜索嵌入對象中鍵的範圍來聚合MongoDB

{ 
    _id: "201010/site-1/apache_pb.gif", 
    metadata: { 
     date: ISODate("2000-10-00T00:00:00Z"), 
     site: "site-1", 
     page: "/apache_pb.gif" }, 
    daily: { 
     "1": {"sessions": 300, "bounces": 10} 
     "2": {"sessions": 100, "bounces": 5}, 
     "3": {"sessions": 10}, 
     "4": {"sessions": 100, "bounces": 4}, 
     ... } 
} 

例如,檢索數據特定日期

db.stats.monthly.find_one({ },{ 'daily.1': 1 'metadata': 1}) 

上述模式對我來說是最記錄使用情況的偉大工程爲好,因爲它本質上只是查找。

對於少數情況下,我們可能有自定義日期範圍是我在努力,所以如果用戶搜索1月1日 - 3月1日=>我理想地期待這個結果。

[{ 
     _id: "201010/site-1/apache_pb.gif", 
     metadata: { 
      date: ISODate("2000-10-00T00:00:00Z"), 
      site: "site-1", 
      page: "/apache_pb.gif" }, 
     result: { 
      "sessions": 410, "bounces": 15 } 
    }, { 

}, { 
     _id: "201010/site-1/apache_new.gif", 
     metadata: { 
      date: ISODate("2000-05-00T00:00:00Z"), 
      site: "site-1", 
      page: "/apache_new.gif" }, 
     result: { 
      "sessions": 310, "bounces": 8 } 
    }, { 

}... 
] 

我明白,我們需要做的聚集,但如果它甚至可以通過給一系列適用於嵌入式對象鍵到匯聚完全糊塗了。

我需要重構我的模式才能做到這一點嗎?我真的很喜歡高效的查找,他們服務於我們用例的80-90%。

回答

3

您當前的數據存儲格式在聚合框架或一般的MongoDB查詢中表現不佳。核心問題是你的「日常」對象只包含每個項目的命名鍵。這意味着爲了訪問任何東西,需要爲MongoDB提供指向該密鑰的特定路徑。如"daily.1",就像你提到的一樣。

如前所述,聚合框架和一般的MongoDB操作不能「遍歷對象的鍵」,所以你需要服務器端JavaScript來收集所有鍵的數據。

的做法,是更符合優化的MongoDB capabilties,是存儲在一個數組,而不是「人民日報」的數據:

{ 
    _id: "201010/site-1/apache_pb.gif", 
    metadata: { 
     date: ISODate("2000-10-00T00:00:00Z"), 
     site: "site-1", 
     page: "/apache_pb.gif" 
    }, 
    daily: [ 
     { "day": 1, "sessions": 300, "bounces": 10}, 
     { "day": 2, "sessions": 100, "bounces": 5}, 
     { "day": 3, "sessions": 10}, 
     { "day": 4, "sessions": 100, "bounces": 4} 
    ] 
} 

然後你就可以在內容很簡單,運行聚合:

db.colllection.aggregate([ 
    // Match relevant objects 
    { "$match": { 
     "daily": { 
      "$elemMatch": { 
       "day": { "$gte": 1, "$lte": 3 } 
      } 
     } 
    }}, 

    // Unwind to denormalize array 
    { "$unwind": "$daily" }, 

    // Filter the required results 
    { "$match": { 
     "daily.day": { "$gte": 1, "$lte": 3 } 
    }}, 
    // Group data and sum totals 
    { "$group": { 
     "_id": "$_id", 
     "metadata": { "$first": "$metadata" }, 
     "resultSessions": { "$sum": "$daily.sessions" }, 
     "resultBounces": { "$sum": "$daily.bounces" } 
    }}, 

    // Optionally project to desired format 
    { "$project": { 
     "metadata": 1, 
     "result": { 
      "sessions": "$resultSessions", 
      "bounces": "$resultBounces" 
     } 
    }} 
]) 

或者更好的是,平倉前陣預過濾器:

db.colllection.aggregate([ 
    { "$match": { 
     "daily": { 
      "$elemMatch": { 
       "day": { "$gte": 1, "$lte": 3 } 
      } 
     } 
    }}, 
    { "$project": { 
     "metadata": 1, 
     "daily": { 
      "$setDifference": [ 
       { "$map": { 
        "input": "$daily", 
        "as": "day", 
        "in": { 
         "$cond": [ 
          { "$and": [ 
           { "$gte": [ "$day.day", 1 ] }, 
           { "$lte": [ "$day.day", 3 ] } 
          ]}, 
          "$day", 
          false 
         ] 
        } 
       }}, 
       [false] 
      ] 
     } 
    }}, 
    { "$unwind": "$daily" }, 
    { "$group": { 
     "_id": "$_id", 
     "metadata": { "$first": "$metadata" }, 
     "resultSessions": { "$sum": "$daily.sessions" }, 
     "resultBounces": { "$sum": "$daily.bounces" } 
    }}, 
    { "$project": { 
     "metadata": 1, 
     "result": { 
      "sessions": "$resultSessions", 
      "bounces": "$resultBounces" 
     } 
    }} 
]) 

並且請始終請$match相關對象先減少正在處理的內容。

由於數據中的屬性現在共享所有相同的路徑,並且不受外鍵的約束,現在可以輕鬆累積它們。

如果沒有這種結構變化,聚集服務器上的唯一方式是使用MapReduce的,它可以用一個編碼函數進行迭代的對象鍵:

db.collection.mapReduce(
    function() { 
     var result = { "sessions": 0, "bounces": 0 }; 
     Object.keys(this.daily) 
      .filter(function(key) { 
       return (key >= 1 && key <= 3); 
      }) 
      .forEach(function(key) { 
       result.sessions += this.daily[key].sessions; 
       result.bounces += this.daily[key].bounces; 
      }); 
     emit(this._id,{ metadata: this.metadata, result: result }); 
    }, 
    function() {}, // won't be called for unique _id values 
    { 
     "out": { "inline": 1 }, 
     "query": { 
      "daily": { 
       "$elemMatch": { 
        "day": { "$gte": 1, "$lte": 3 } 
       } 
     } 
    }}, 


    } 
) 
在這兩種情況下調整取決於分組

當然關於你是否打算跨文件累積。

當然,如果您實際上並沒有在文檔中累積,那麼只需在您自己的客戶端接收代碼中使用相同類型的密鑰遍歷。

+0

謝謝布雷克斯七...有點不同的攻擊,現在,但我會在一段時間內回到你身邊。順便說一句,我們使用mongo的唯一原因是預先計算並在那裏存儲東西,這樣我們就可以進行高效的查找。 95%的查詢是針對特定的幾天,幾個月,幾周 - 最好採用最初的方法,只是針對關閉的情況進行map-reduce?我假設使用聚合總是會付出代價的,或者是時間上的差異不會被注意到...... –

+0

這真的取決於你在做什麼。隨着交替,返回「特定」單數匹配不成問題。你所需要做的只是匹配查詢中的''daily.day''屬性並使用[位置'$'](https://docs.mongodb.org/manual/reference/operator/projection/positional/)運算符返回匹配的元素。上述另一種情況是,如果這總是隻有單個文檔響應(或僅從每個文檔中過濾),那麼只需返回數據並在客戶端代碼中循環過濾即可。 –

+0

也許這兩種模式設計的一個主要缺點是更新查詢....一個是更新新的日子,將被推入該數組,另一個更新數據已經存在了一天(如會話修改爲105第二天) –