【问题标题】:Update with upsert, but only update if date field of document in db is less than updated document使用 upsert 更新,但仅当 db 中文档的日期字段小于更新的文档时才更新
【发布时间】:2017-05-14 07:43:59
【问题描述】:

我在尝试为此提出逻辑时遇到了一些问题。所以,我想做的是:

  • 将一堆帖子批量更新到我的远程 MongoDB 实例 BUT
  • 如果更新,仅当远程集合上的lastModified 字段小于我将要更新/插入的同一文档中的lastModified 字段时才更新

基本上,我想更新我的文档列表,如果它们自上次更新以来已被修改。 我可以想到两种蛮力方法来做到这一点......

首先,查询我的整个集合,尝试手动删除和替换符合条件的文档,添加新文档,然后在删除远程中的所有内容后将所有内容大量插入远程集合​​。

其次,查询每个项目,然后决定,如果远程有一个,我是否要更新它。在处理远程集合时,这似乎是一项非常艰巨的任务。

如果相关,我在 NodeJS 环境中工作,使用 mondodb npm 包进行数据库操作。

【问题讨论】:

  • 您是否使用另一个集合中的值更新文档?
  • 不,我正在从对外部服务的 http 调用中收集值。

标签: mongodb mongodb-query


【解决方案1】:

您可以使用 bulkWrite API 根据您指定的逻辑执行更新,因为它可以更好地处理此问题。

例如,下面的 sn-p 显示了如何执行此操作,假设您已经拥有来自 Web 服务的数据,您需要使用它来更新远程集合:

mongodb.connect(mongo_url, function(err, db) {
    if(err) console.log(err);
    else {
        var mongo_remote_collection = db.collection("remote_collection_name");

        /* data is from http call to an external service or ideally
           place this within the service callback
        */
        mongoUpsert(mongo_remote_collection, data, function() {
            db.close();
        })
    }
})

function mongoUpsert(collection, data_array, cb) {      
    var ops = data_array.map(function(data) {
        return {
            "updateOne": {
                "filter": { 
                    "_id": data._id, // or any other filtering mechanism to identify a doc
                    "lastModified": { "$lt": data.lastModified }
                },
                "update": { "$set": data },
                "upsert": true
            }
        };
    });

    collection.bulkWrite(ops, function(err, r) {
        // do something with result
    });

    return cb(false);
}

如果来自外部服务的数据很大,那么考虑将写入分批发送到服务器,每批 500 次,这样可以提高性能,因为您不会将每个请求都发送到服务器,而是每 500 个请求发送一次。

对于批量操作,MongoDB 对每个批次强制执行 default internal limit 1000 个操作,因此选择 500 个文档是好的,因为您可以对批次大小进行一些控制,而不是让 MongoDB 强制执行默认值,即对于较大的操作> 1000 个文档的大小。因此,对于第一种方法中的上述情况,可以一次写入所有数组,因为这很小,但 500 选择用于更大的数组。

var ops = [],
    counter = 0;

data_array.forEach(function(data) {
    ops.push({
        "updateOne": {
            "filter": { 
                "_id": data._id,
                "lastModified": { "$lt": data.lastModified }
            },
            "update": { "$set": data },
            "upsert": true
        }
    });
    counter++;

    if (counter % 500 === 0) {
        collection.bulkWrite(ops, function(err, r) {
            // do something with result
        });
        ops = [];
    }
})

if (counter % 500 != 0) {
    collection.bulkWrite(ops, function(err, r) {
        // do something with result
    }
}

【讨论】:

  • 在这种情况下,找到带有ID但不满足lastModified条件的文档(本地的lastModified小于远程的lastModified),仍然会插入到集合中,不是吗?由于它们不属于过滤器参数
  • 我现在不能写出一个解释清楚的答案,所以我把它作为评论。:) 你需要的是 $max 更新运算符。
  • @Styvane 但是$max 只会将lastModified 字段的值更新为来自其他集合的指定值,如果指定值大于该字段的当前值。其他领域呢?
  • 哼!好问题,我没想到。在这种情况下,$max 将没有用,但您只需将 bulkWriteordered 选项设置为 false 到 avoiding the operations to stop after a duplication key error 并在 try/catch 块中执行所有操作,并适当地处理异常。跨度>
  • 等一下,如果“本地的lastModified小于远程的lastModified”,这种情况下不会插入本地文档,因为mongo会抛出重复键错误,对吧?不满足过滤条件,由于“upsert: true”,mongo 将尝试插入带有 _id 的文档,插入将失败,因为带有该 _id 的文档已经存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-19
相关资源
最近更新 更多