[英]How to sum values in a nested date range in MongoDB
對於您現在想要執行的那種操作,這確實不是一個很好的結構。 以這種格式保存數據的全部目的是隨您的需要“遞增”數據。
例如:
var now = Date.now(),
today = new Date(now - ( now % ( 1000 * 60 * 60 * 24 ))).toISOString().substr(0,10);
var product = "Product_123";
db.counters.updateOne(
{
"month": today.substr(0,7),
"product": product
},
{
"$inc": {
[`dates.${today}`]: 1,
"totals": 1
}
},
{ "upsert": true }
)
這樣,使用$inc
的后續更新既適用於“日期”所使用的“鍵”,又適用於匹配文檔的“總計”屬性。 因此,經過幾次迭代,您最終會得到如下結果:
{
"_id" : ObjectId("5af395c53945a933add62173"),
"product": "Product_123",
"month": "2018-05",
"dates" : {
"2018-05-10" : 2,
"2018-05-09" : 1
},
"totals" : 3
}
如果您實際上並沒有這樣做,那么您應該“這樣做”,因為它是這種結構的預期使用模式。
在存儲這些關鍵字的文檔中不保留“總計”或類似條目類型的情況下,處理中留給“聚合”的唯一方法是有效地將“關鍵字”強制轉換為“數組”形式。
db.colllection.aggregate([
// Only consider documents with entries within the range
{ "$match": {
"$expr": {
"$anyElementTrue": {
"$map": {
"input": { "$objectToArray": "$days" },
"in": {
"$and": [
{ "$gte": [ "$$this.k", "2018-06-01" ] },
{ "$lt": [ "$$this.k", "2018-07-01" ] }
]
}
}
}
}
}},
// Aggregate for the month
{ "$group": {
"_id": "$product", // <-- or whatever your key for the value is
"total": {
"$sum": {
"$sum": {
"$map": {
"input": { "$objectToArray": "$days" },
"in": {
"$cond": {
"if": {
"$and": [
{ "$gte": [ "$$this.k", "2018-06-01" ] },
{ "$lt": [ "$$this.k", "2018-07-01" ] }
]
},
"then": "$$this.v",
"else": 0
}
}
}
}
}
}
}}
])
db.collection.mapReduce(
// Taking the same presumption on your un-named key for "product"
function() {
Object.keys(this.days)
.filter( k => k >= "2018-06-01" && k < "2018-07-01")
.forEach(k => emit(this.product, this.days[k]));
},
function(key,values) {
return Array.sum(values);
},
{
"out": { "inline": 1 },
"query": {
"$where": function() {
return Object.keys(this.days).some(k => k >= "2018-06-01" && k < "2018-07-01")
}
}
}
)
兩者都非常可怕,因為您甚至需要計算“密鑰”是否落在要求的范圍內,甚至選擇文檔,甚至還要再次篩選那些文檔中的密鑰以決定是否進行累積。
在此還要注意,如果您的"Product_123'
也是文檔中的“鍵名”而不是“值”,那么您將執行更多的“體操”操作,以將“鍵”簡單地轉換為“值”形式,這是數據庫的工作方式以及不必要的強制措施的全部內容。
因此,與最初顯示的處理方式相反,在每次對文檔進行每次寫操作時,您都應該“隨身攜帶”累積的內容,比強制處理為數組格式的“處理”更好的選擇是首先簡單地將數據放入數組中:
{
"_id" : ObjectId("5af395c53945a933add62173"),
"product": "Product_123",
"month": "2018-05",
"dates" : [
{ "day": "2018-05-09", "value": 1 },
{ "day": "2018-05-10", "value": 2 }
},
"totals" : 3
}
這些對於查詢和進一步分析是絕對更好的:
db.counters.aggregate([
{ "$match": {
// "month": "2018-05" // <-- or really just that, since it's there
"dates": {
"day": {
"$elemMatch": {
"$gte": "2018-05-01", "$lt": "2018-06-01"
}
}
}
}},
{ "$group": {
"_id": null,
"total": {
"$sum": {
"$sum": {
"$filter": {
"input": "$dates",
"cond": {
"$and": [
{ "$gte": [ "$$this.day", "2018-05-01" ] },
{ "$lt": [ "$$this.day", "2018-06-01" ] }
]
}
}
}
}
}
}}
])
這當然是非常有效的,並且有意避免了僅用於演示的"total"
字段。 但是,您當然可以通過執行以下操作來保持“運行累積”:
db.counters.updateOne(
{ "product": product, "month": today.substr(0,7)}, "dates.day": today },
{ "$inc": { "dates.$.value": 1, "total": 1 } }
)
這真的很簡單。 添加upserts會增加“一點點”的復雜性:
// A "batch" of operations with bulkWrite
db.counter.bulkWrite([
// Incrementing the matched element
{ "udpdateOne": {
"filter": {
"product": product,
"month": today.substr(0,7)},
"dates.day": today
},
"update": {
"$inc": { "dates.$.value": 1, "total": 1 }
}
}},
// Pushing a new "un-matched" element
{ "updateOne": {
"filter": {
"product": product,
"month": today.substr(0,7)},
"dates.day": { "$ne": today }
},
"update": {
"$push": { "dates": { "day": today, "value": 1 } },
"$inc": { "total": 1 }
}
}},
// "Upserting" a new document were not matched
{ "updateOne": {
"filter": {
"product": product,
"month": today.substr(0,7)},
},
"update": {
"$setOnInsert": {
"dates": [{ "day": today, "value": 1 }],
"total": 1
}
},
"upsert": true
}}
])
但是通常,您可以通過“隨身攜帶”積累一些簡單的東西,並在以后查詢和進行其他分析時輕松而高效地獲取“兩全其美”的東西。
故事的總體寓意是為您實際想要做的事情“選擇正確的結構”。 不要將東西放到明顯打算用作“值”的“鍵”中,因為這是一種反模式,只會為您的其余目的增加復雜性和效率,即使對於“單個”而言似乎正確最初以這種方式存儲時的目的。
注意同樣也不建議在此處以任何方式為“日期”存儲“字符串”。 如前所述,更好的方法是使用“值”,即您真正要使用的“值”。 當將日期數據存儲為“值”時,將其存儲為BSON日期而不是“字符串” 總是更加高效和實用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.