您當前的數據存儲格式在聚合框架或一般的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 }
}
}
}},
}
)
在這兩種情況下調整取決於分組
當然關於你是否打算跨文件累積。
當然,如果您實際上並沒有在文檔中累積,那麼只需在您自己的客戶端接收代碼中使用相同類型的密鑰遍歷。
謝謝布雷克斯七...有點不同的攻擊,現在,但我會在一段時間內回到你身邊。順便說一句,我們使用mongo的唯一原因是預先計算並在那裏存儲東西,這樣我們就可以進行高效的查找。 95%的查詢是針對特定的幾天,幾個月,幾周 - 最好採用最初的方法,只是針對關閉的情況進行map-reduce?我假設使用聚合總是會付出代價的,或者是時間上的差異不會被注意到...... –
這真的取決於你在做什麼。隨着交替,返回「特定」單數匹配不成問題。你所需要做的只是匹配查詢中的''daily.day''屬性並使用[位置'$'](https://docs.mongodb.org/manual/reference/operator/projection/positional/)運算符返回匹配的元素。上述另一種情況是,如果這總是隻有單個文檔響應(或僅從每個文檔中過濾),那麼只需返回數據並在客戶端代碼中循環過濾即可。 –
也許這兩種模式設計的一個主要缺點是更新查詢....一個是更新新的日子,將被推入該數組,另一個更新數據已經存在了一天(如會話修改爲105第二天) –