更新文档 - 按日期过滤的以前文档的平均值

时间:2017-11-03 23:00:03

标签: python mongodb aggregation-framework pymongo

我有很多文件,如:

{
  _id: something
  date: 2017-10-23 20:05:00.000 (Date)
  qty: 400.41 (Double)
  weight: 100.44 (Double)
}

我需要通过添加2个字段来更新每个文档:qty_avg和weight_avg,这些字段是按日期后最多5分钟的元素的平均值计算的。

我可以使用的工具是python或mongo控制台,如果可能的话,后者会更好

1 个答案:

答案 0 :(得分:1)

就像我在评论中所说,这不是"聚合"查询。聚合不会更新"本身,但返回的结果,你可以"任选"输出到 new 集合,或者只是迭代光标。

您实际要求的内容涉及"迭代光标",您基本上记录时间范围内的最后一个值,然后相应地计算和更新文档。

使用pymongo,但对任何语言的练习仍然相同:

from datetime import datetime, timedelta

docBuffer = []
writeBuffer = []

for doc in collection.find().sort('date',pymongo.ASCENDING):
  docBuffer.append(doc)        # Add to buffer

  # Filter buffer for expired
  docBuffer = filter(lambda x: x['date'] >= (doc['date'] - timedelta(minutes=5)), docBuffer)

  writeBuffer.append({
    "updateOne": {
      "filter": { "_id": doc['_id'] },
      "update": {
        "$set": {
          "qty_avg": reduce(lambda x,y: y['qty'] + x, docBuffer, 0) / len(docBuffer),
          "weight_avg": reduce(lambda x, y: y['weight'] + x, docBuffer, 0) / len(docBuffer)
        }
      }
    }
  })

  # Write if buffer has enough to make a bulk write
  if len(writeBuffer) > 1000:
    collection.bulk_write(writeBuffer);
    writeBuffer = []

# Clear any buffered writes
if len(writeBuffer) > 0:
  collection.bulk_write(writeBuffer);
  writeBuffer = []

或者shell的纯JavaScript。差别不大:

var docBuffer = [],
   writeBuffer = [];

db.collection.find().sort({ "date": 1 }).forEach( doc => {
  docBuffer.push(doc);

  docBuffer = docBuffer.filter( x => x.date.valueOf() > (doc.date - (1000 * 60 * 5)) );

  writeBuffer.push({
    "updateOne": {
      "filter": { "_id": doc._id },
      "update": {
        "$set": {
          "avg_qty": docBuffer.reduce((x,y) => y.qty + x, 0 ) / docBuffer.length,
          "avg_weight": docBuffer.reduce((x,y) => y.weight + x, 0 ) / docBuffer.length
        }
      }
    }
  });

  if (writeBuffer.length > 1000) {
    db.collection.bulkWrite(writeBuffer);
    writeBuffer = [];
  }

});

if (writeBuffer.length > 0) {
  db.collection.bulkWrite(writeBuffer);
  writeBuffer = [];
}

所以基本上你会查看每个文档并将相关数据保存在列表中。你真的只需要你想要平均值的日期和字段,而只需要推送整个文档,因为它在你的问题中很小。然后,您filter列出任何超过所需日期"窗口的日期"这样他们就被删除了。

然后我们来回写这个集合,在这里使用bulk_write(),因为它是最有效的事情,因为它只是在我们告诉时实际写入,并且在&#34中这样做;批次"这样处理的每个文件都不会立即导致它自己写入并减慢进程。这种方式的开销较少。

我们所做的只是$set新字段,计算"缓冲区"的平均值。在我们过滤了"已过期"的日期后,仍然存在的条目列表从窗口。

基本上就是这样。

MongoDB 3.6 - 可以加入,但不是最佳。

当MongoDB 3.6发布时,它可能是"可能"使用" join"运行聚合条件使用新的"子管道" $lookup。但它确实远不如光标那么接近每个项目自我引用的明显原因:

[
  { "$lookup": {
    "from": "collection",
    "vars": { "currDate": "$date" },
    "pipeline": [
      { "$match": {
        "$expr": {
          "$and": [
            { "$gte": ["$date", { "$subtract": ["$$currDate", (1000 * 60 * 5 )] }] },
            { "$lte": ["$date", "$$currDate"] }
          ]
        }
      }}
    ],
    "as": "buffer"
  }},
  { "$project": {
    "date": 1,
    "qty": 1,
    "weight": 1,
    "avg_qty": { "$avg": "$buffer.qty" },
    "avg_weight": { "$avg": "$buffer.weight" }
  }}
]

这基本上是你获得这样一个"窗口的唯一方式。来自"聚合",正如你所看到的那样,并不是一个"聚合"根本就是一个人为的"加入"条件允许"跳过"获取当前文档的相对日期。

所以你通常更好地坚持使用光标,即使它确实意味着写入过程不能完全包含在服务器本身上并且需要一些通过有线通信。