聚合 $group 用于多个日期范围

21

就我的聚合而言,流中的每个文档都会有一个日期。

我需要在日期范围内求和一些值。

我的文档长这样:

{ 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天前8-15天前以及15-30天前

我可能需要使用3个不同的聚合查询,并在每个查询中使用不同的$match来处理日期。

但是是否有可能在一次运行中完成所有的$group操作并对"value"字段求和呢?


我认为你不能一次性完成那个。你应该真正去做3次。 - Mateo Barahona
@MateoBarahona 当然可以一次性完成。这在很大程度上是运算符(例如$cond)的主要用途。 - Blakes Seven
@Blakes Seven: 哦,你说得对 - Mateo Barahona
2个回答

39

根据当前日期在范围内的位置有条件地确定分组键。这基本上是通过使用嵌套条件和$cond的逻辑变量$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是三元运算符,第一个条件被评估以查看条件是否为真,当为真时返回第二个参数,否则在假的情况下返回第三个参数。因此,通过在假的情况下嵌套另一个$cond,您可以获得日期落在哪个逻辑测试上的结果,即“小于15天的日期”表示它在最旧的范围内,或者“小于7天”表示它在中间范围内,或当然,它在最新的范围内。

我只是在这里使用0来为小于10的数字添加前缀,以便在需要排序时提供输出,因为$group中的“键”本身并没有按顺序排列。

但这就是您在单个查询中执行此操作的方式。您只需根据日期所在的位置确定分组键,并累积每个键。


+1 太棒了!只是我认为你应该交换条件。 我已经测试过了,它运行得很好... 干得好,Blakes! - Amir
@amir 请分享。 - Ahmad Hassan

2
这是一个很好的使用$bucket阶段的案例,结合在Mongo 5中引入的$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天的文档
  • 最后根据boundaries: [-30, -15, -7, 1]定义的边界将文档分组到不同的桶中,每个桶中的文档都有相同的diff
    • 对于每个桶,我们将桶中的value相加

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接