聚合多个日期范围的 $group
Aggregate $group for multiple date ranges
在我看来,流中的每个文档都会有一个日期。
我需要对日期范围内的一些值求和..
我的文件看起来像:
{ value: 3, date: [SoME TIME STAMP] },
{ value: 4, date: [SoME TIME STAMP] },
{ value: 1, date: [SoME TIME STAMP] },
{ value: -6, date: [SoME TIME STAMP] }
我希望能够根据日期范围对这些文档进行分组。即:1-7 days ago
、8-15 days ago
。和 15-30 days ago
.
我可能在日期上应用 3 个不同的聚合查询和 3 个不同的 $match。
但是是否有可能完成所有 $group 并将“值”字段求和在一个 运行 中?
第一步是创建代表您的范围的日期对象。假设您想 运行 为 8-15 天前的危险进行聚合操作,这意味着您需要两个日期对象,比如说开始和结束。 start 将保留一天前的日期,end 将保留 8 天前的日期。创建这些日期对象很容易,只需将日期减去 n
即可将它们设置为之前的天数,其中 n
是天前的天数:
var start = new Date();
start.setDate(start.getDate() - 8);
var end = new Date();
end.setDate(end.getDate() - 15);
或使用 .getTime()
方法从时间戳毫秒中减去 returns 标准 JavaScript 时间戳(自 Jan 1/1970
以来的毫秒数),您可以在其上使用常规数学运算,以及直接反馈给Date对象:
var today = new Date();
var start = new Date(today.getTime() - 8*24*60*60*1000);
var end = new Date(today.getTime() - 15*24*60*60*1000);
现在您有了日期对象,您可以将它们用作 $match
criteria, utilising the $lte
and $gte
比较运算符:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
}
]
运行 此阶段的聚合将为您提供日期在 8-15 天前范围内的所有文档,
db.aggregate(pipeline);
相当于find()
查询:
db.collection.find({
"date": { "$gte": start, "$lte": end }
});
现在,到下一个管道阶段,您需要创建一个聚合操作,指定一组 _id
为 null,使用 [= 计算集合中所有文档的总值和计数43=]$sum
累加器运算符:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
]
db.collection.aggregate(pipeline);
您甚至可以进一步创建一个通用函数,该函数 returns 上述聚合操作的实际总计采用两个参数,即日期范围的起始值和结束值:
var getTotalValues = function(start, end){
var today = new Date();
var startDate = new Date(today.getTime() - start*24*60*60*1000);
var endDate = new Date(today.getTime() - end*24*60*60*1000);
var pipeline = [
{
"$match": {
"timestamp": { "$gte": startDate, "$lte": endDate }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
],
resultArray = db.collection.aggregate(pipeline).toArray();
return resultArray[0].totalValues;
}
var total = getTotalValues(1, 8);
printjson(total); // prints the total
您需要根据当前日期在范围内的位置有条件地确定分组键。这基本上是通过 $cond
with nested condtions and the logical variant of $lt
:
实现的
// work out dates somehow
var today = new Date(),
oneDay = ( 1000 * 60 * 60 * 24 ),
thirtyDays = new Date( today.valueOf() - ( 30 * oneDay ) ),
fifteenDays = new Date( today.valueOf() - ( 15 * oneDay ) ),
sevenDays = new Date( today.valueOf() - ( 7 * oneDay ) );
db.collection.aggregate([
{ "$match": {
"date": { "$gte": thirtyDays }
}},
{ "$group": {
"_id": {
"$cond": [
{ "$lt": [ "$date", fifteenDays ] },
"16-30",
{ "$cond": [
{ "$lt": [ "$date", sevenDays ] },
"08-15",
"01-07"
]}
]
},
"count": { "$sum": 1 },
"totalValue": { "$sum": "$value" }
}}
])
由于$cond
是一个三元运算符,第一个条件被评估以查看条件是否为真,如果为真则返回第二个参数,否则返回第三个参数为假。因此,通过在 false 情况下嵌套另一个 $cond
,您可以对日期所在的位置进行逻辑测试,"less that the 15 day date" 表示它在最旧的范围内,或者 "less than 7 days" 表示中间范围,或者当然是最新的范围。
我只是在此处小于 10 的数字前加上 0
,因此如果您愿意,它可以为您提供一些排序依据,因为 "keys" 在 $group
中的输出本身不是有序的。
但这就是您在单个查询中执行此操作的方式。您只需根据日期所在的位置计算出分组键应该是什么,然后为每个键累加。
这是 Mongo 5
中介绍的 $bucket
stage, combined with $dateDiff
的一个很好的用例:
// { date: ISODate("2021-12-04"), value: 3 } <= last 7 days
// { date: ISODate("2021-11-25"), value: 5 } <= last 15 days
// { date: ISODate("2021-11-24"), value: 1 } <= last 15 days
// { date: ISODate("2021-11-12"), value: 12 } <= last 30 days
// { date: ISODate("2021-10-04"), value: 8 } <= too old
db.collection.aggregate([
{ $set: {
diff: { $dateDiff: { startDate: "$$NOW", endDate: "$date", unit: "day" } }
}},
// { value: 3, diff: 0 }
// { value: 5, diff: -9 }
// { value: 1, diff: -10 }
// { value: 12, diff: -22 }
// { value: 8, diff: -61 }
{ $match: { diff: { $gte: -30 } } },
// { value: 3, diff: 0 }
// { value: 5, diff: -9 }
// { value: 1, diff: -10 }
// { value: 12, diff: -22 }
{ $bucket: {
groupBy: "$diff",
boundaries: [-30, -15, -7, 1],
output: { total: { $sum: "$value" } }
}}
])
// { _id: -30, total: 12 } <= 30 to 16 days ago
// { _id: -15, total: 6 } <= 15 to 8 days ago
// { _id: -7, total: 3 } <= 7 to 0 days ago
这个:
- 首先计算(使用
$dateDiff
)今天("$$NOW"
)与文档的 date
之间相差的天数
- 如果日期是 3 天前,
diff
将设置为 -3
- 然后根据
diff
过滤掉超过 30 天的任何文档
- 最后根据
diff
将文档存储在边界由 boundaries: [-30, -15, -7, 1]
定义的存储桶中
- 并且对于每个桶,我们对桶
value
s 求和
在我看来,流中的每个文档都会有一个日期。
我需要对日期范围内的一些值求和..
我的文件看起来像:
{ value: 3, date: [SoME TIME STAMP] },
{ value: 4, date: [SoME TIME STAMP] },
{ value: 1, date: [SoME TIME STAMP] },
{ value: -6, date: [SoME TIME STAMP] }
我希望能够根据日期范围对这些文档进行分组。即:1-7 days ago
、8-15 days ago
。和 15-30 days ago
.
我可能在日期上应用 3 个不同的聚合查询和 3 个不同的 $match。
但是是否有可能完成所有 $group 并将“值”字段求和在一个 运行 中?
第一步是创建代表您的范围的日期对象。假设您想 运行 为 8-15 天前的危险进行聚合操作,这意味着您需要两个日期对象,比如说开始和结束。 start 将保留一天前的日期,end 将保留 8 天前的日期。创建这些日期对象很容易,只需将日期减去 n
即可将它们设置为之前的天数,其中 n
是天前的天数:
var start = new Date();
start.setDate(start.getDate() - 8);
var end = new Date();
end.setDate(end.getDate() - 15);
或使用 .getTime()
方法从时间戳毫秒中减去 returns 标准 JavaScript 时间戳(自 Jan 1/1970
以来的毫秒数),您可以在其上使用常规数学运算,以及直接反馈给Date对象:
var today = new Date();
var start = new Date(today.getTime() - 8*24*60*60*1000);
var end = new Date(today.getTime() - 15*24*60*60*1000);
现在您有了日期对象,您可以将它们用作 $match
criteria, utilising the $lte
and $gte
比较运算符:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
}
]
运行 此阶段的聚合将为您提供日期在 8-15 天前范围内的所有文档,
db.aggregate(pipeline);
相当于find()
查询:
db.collection.find({
"date": { "$gte": start, "$lte": end }
});
现在,到下一个管道阶段,您需要创建一个聚合操作,指定一组 _id
为 null,使用 [= 计算集合中所有文档的总值和计数43=]$sum
累加器运算符:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
]
db.collection.aggregate(pipeline);
您甚至可以进一步创建一个通用函数,该函数 returns 上述聚合操作的实际总计采用两个参数,即日期范围的起始值和结束值:
var getTotalValues = function(start, end){
var today = new Date();
var startDate = new Date(today.getTime() - start*24*60*60*1000);
var endDate = new Date(today.getTime() - end*24*60*60*1000);
var pipeline = [
{
"$match": {
"timestamp": { "$gte": startDate, "$lte": endDate }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
],
resultArray = db.collection.aggregate(pipeline).toArray();
return resultArray[0].totalValues;
}
var total = getTotalValues(1, 8);
printjson(total); // prints the total
您需要根据当前日期在范围内的位置有条件地确定分组键。这基本上是通过 $cond
with nested condtions and the logical variant of $lt
:
// work out dates somehow
var today = new Date(),
oneDay = ( 1000 * 60 * 60 * 24 ),
thirtyDays = new Date( today.valueOf() - ( 30 * oneDay ) ),
fifteenDays = new Date( today.valueOf() - ( 15 * oneDay ) ),
sevenDays = new Date( today.valueOf() - ( 7 * oneDay ) );
db.collection.aggregate([
{ "$match": {
"date": { "$gte": thirtyDays }
}},
{ "$group": {
"_id": {
"$cond": [
{ "$lt": [ "$date", fifteenDays ] },
"16-30",
{ "$cond": [
{ "$lt": [ "$date", sevenDays ] },
"08-15",
"01-07"
]}
]
},
"count": { "$sum": 1 },
"totalValue": { "$sum": "$value" }
}}
])
由于$cond
是一个三元运算符,第一个条件被评估以查看条件是否为真,如果为真则返回第二个参数,否则返回第三个参数为假。因此,通过在 false 情况下嵌套另一个 $cond
,您可以对日期所在的位置进行逻辑测试,"less that the 15 day date" 表示它在最旧的范围内,或者 "less than 7 days" 表示中间范围,或者当然是最新的范围。
我只是在此处小于 10 的数字前加上 0
,因此如果您愿意,它可以为您提供一些排序依据,因为 "keys" 在 $group
中的输出本身不是有序的。
但这就是您在单个查询中执行此操作的方式。您只需根据日期所在的位置计算出分组键应该是什么,然后为每个键累加。
这是 Mongo 5
中介绍的 $bucket
stage, combined with $dateDiff
的一个很好的用例:
// { date: ISODate("2021-12-04"), value: 3 } <= last 7 days
// { date: ISODate("2021-11-25"), value: 5 } <= last 15 days
// { date: ISODate("2021-11-24"), value: 1 } <= last 15 days
// { date: ISODate("2021-11-12"), value: 12 } <= last 30 days
// { date: ISODate("2021-10-04"), value: 8 } <= too old
db.collection.aggregate([
{ $set: {
diff: { $dateDiff: { startDate: "$$NOW", endDate: "$date", unit: "day" } }
}},
// { value: 3, diff: 0 }
// { value: 5, diff: -9 }
// { value: 1, diff: -10 }
// { value: 12, diff: -22 }
// { value: 8, diff: -61 }
{ $match: { diff: { $gte: -30 } } },
// { value: 3, diff: 0 }
// { value: 5, diff: -9 }
// { value: 1, diff: -10 }
// { value: 12, diff: -22 }
{ $bucket: {
groupBy: "$diff",
boundaries: [-30, -15, -7, 1],
output: { total: { $sum: "$value" } }
}}
])
// { _id: -30, total: 12 } <= 30 to 16 days ago
// { _id: -15, total: 6 } <= 15 to 8 days ago
// { _id: -7, total: 3 } <= 7 to 0 days ago
这个:
- 首先计算(使用
$dateDiff
)今天("$$NOW"
)与文档的date
之间相差的天数- 如果日期是 3 天前,
diff
将设置为-3
- 如果日期是 3 天前,
- 然后根据
diff
过滤掉超过 30 天的任何文档
- 最后根据
diff
将文档存储在边界由boundaries: [-30, -15, -7, 1]
定义的存储桶中- 并且对于每个桶,我们对桶
value
s 求和
- 并且对于每个桶,我们对桶