【问题标题】:Query mongo to detect value changes in time series查询mongo检测时间序列的值变化
【发布时间】:2019-04-10 06:54:53
【问题描述】:

我想知道在 MongoDB 中是否可以进行以下操作。

我收集了代表某个时间值变化的文档:

{
  "day" : ISODate("2018-12-31T23:00:00.000Z"),
  "value": [some integer value]
}

数据中没有“漏洞”,我有一段时间内所有日子的条目。

是否可以查询此集合以仅获取与前一个具有不同值的文档(按天 asc 排序时)?例如,有以下文件:

{ day: ISODate("2019-04-01T00:00:00.000Z"), value: 10 }
{ day: ISODate("2019-04-02T00:00:00.000Z"), value: 10 }
{ day: ISODate("2019-04-03T00:00:00.000Z"), value: 15 }
{ day: ISODate("2019-04-04T00:00:00.000Z"), value: 15 }
{ day: ISODate("2019-04-05T00:00:00.000Z"), value: 15 }
{ day: ISODate("2019-04-06T00:00:00.000Z"), value: 10 }

我想检索 2018-04-012018-04-032018-04-06 的文档,并且只检索那些因为其他人没有价值变化的文档。

【问题讨论】:

    标签: mongodb mongodb-query time-series aggregation-framework


    【解决方案1】:

    Mongo 5 开始,这是新的$setWindowFields 聚合运算符的完美用例:

    // { day: ISODate("2019-04-01T00:00:00.000Z"), value: 10 } <=
    // { day: ISODate("2019-04-02T00:00:00.000Z"), value: 10 }
    // { day: ISODate("2019-04-03T00:00:00.000Z"), value: 15 } <=
    // { day: ISODate("2019-04-04T00:00:00.000Z"), value: 15 }
    // { day: ISODate("2019-04-05T00:00:00.000Z"), value: 15 }
    // { day: ISODate("2019-04-06T00:00:00.000Z"), value: 10 } <=
    db.collection.aggregate([
    
      { $setWindowFields: {
        sortBy: { day: 1 },
        output: { pair: { $push: "$value", window: { documents: [-1, "current"] } } }
      }},
      // { day: ISODate("2019-04-01T00:00:00Z"), value: 10, pair: [ 10 ] }
      // { day: ISODate("2019-04-02T00:00:00Z"), value: 10, pair: [ 10, 10 ] }
      // { day: ISODate("2019-04-03T00:00:00Z"), value: 15, pair: [ 10, 15 ] }
      // { day: ISODate("2019-04-04T00:00:00Z"), value: 15, pair: [ 15, 15 ] }
      // { day: ISODate("2019-04-05T00:00:00Z"), value: 15, pair: [ 15, 15 ] }
      // { day: ISODate("2019-04-06T00:00:00Z"), value: 10, pair: [ 15, 10 ] }
    
      { $match: { $expr: { $or: [
        { $eq: [ { $size: "$pair" }, 1 ] }, // first doc doesn't have a previous doc
        { $ne: [ { $first: "$pair" }, { $last: "$pair" } ] }
      ]}}},
    
      { $unset: ["pair"] }
    ])
    // { day: ISODate("2019-04-01T00:00:00Z"), value: 10 }
    // { day: ISODate("2019-04-03T00:00:00Z"), value: 15 }
    // { day: ISODate("2019-04-06T00:00:00Z"), value: 10 }
    

    这个:

    • $setWindowFields 聚合阶段开始,该阶段添加了一个 pair 字段,表示当前文档的值和前一个文档的值 (output: { pair: { ... }}):
      • $setWindowFields 为给定文档提供其他文档的视图(window
        • 在我们的例子中是"current" 文档和前一个"-1": window: { documents: [-1, "current"] }
        • 这样我们就可以在这个窗口中构建一个值数组:$push: "$value"
        • 请注意,我们已确保按天对文档进行排序:sortBy: { day: 1 }
    • 然后:
      • 第一个文档中的过滤器(其数组只有一个元素可重新标记):{ $eq: [ { $size: "$pair" }, 1 ] }
      • 如果它们的pair 具有相同的值,则过滤掉以下文档:{ $ne: [ { $first: "$pair" }, { $last: "$pair" } ] }

    【讨论】:

      【解决方案2】:

      您需要获得成对的连续文档来检测差距。为此,您可以将所有文档推送到单个数组中,并且 zip 它本身从头部移动了 1 个元素:

      db.collection.aggregate([
          { $sort: { day: 1 } },
          { $group: { _id: null, docs: { $push: "$$ROOT" } } },
          { $project: {
              pair: { $zip: {
                  inputs:[ { $concatArrays: [ [false], "$docs" ] }, "$docs" ]            
              } }
          } },
          { $unwind: "$pair" },
          { $project: {
              prev: { $arrayElemAt: [ "$pair", 0 ] },
              next: { $arrayElemAt: [ "$pair", 1 ] }
          } },
          { $match: {
               $expr: { $ne: ["$prev.value", "$next.value"] } 
          } },
          { $replaceRoot:{ newRoot: "$next" } }
      ])
      

      剩下的就很简单了——你把数组展开回文档,比较对,过滤掉相等的,然后replaceRoot剩下的。

      【讨论】:

      • 非常感谢您的回答。如果您不介意,我有一个后续问题:是否可以按其他字段向此数据添加分组(例如 userId)?我以为我自己会设法做到这一点,但我遇到了一些麻烦。
      • 当然。如果我的问题正确,您只需将$group: {_id: null 替换为$group: {_id: "$userId"。如果这不是您所追求的,请使用正式数据 sn-ps 和预期结果提出适当的问题。
      猜你喜欢
      • 2017-08-06
      • 2021-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 1970-01-01
      • 2022-10-16
      • 2012-08-28
      相关资源
      最近更新 更多