[英]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.