如何仅在某些情况下插入MongoDB文档?

时间:2015-06-03 19:40:04

标签: mongodb

我正在尝试更新MongoDB集合中的用户文档。如果用户不存在,则应该创建它,但如果它已经存在,则应该只在查询解析某些检查时才更新。

这是我第一次尝试的:

var ts = new Date('2015-01-01T00:00:00.000Z');
var query = { name: 'bob', updatedAt: { $lt: ts } };
var update = { $set: { age: 42, updatedAt: ts }, $inc: { updates: 1 } };
db.users.update(query, update, { upsert: true });

该解决方案的问题在于,如果用户的updatedAt属性小于给定的ts日期,则会尝试创建用户两次。

如果查询的name部分与任何文档不匹配,我怎样才能确保创建它,但如果updatedAt没有做任何事情,我该怎么做?部分没有?

1 个答案:

答案 0 :(得分:3)

基本上:

  • 用户不存在,使用当前时间创建它作为"最后更新时间"。这可以使用简单的upset$setOnInsert;
  • 来完成
  • 用户已存在,需要更新。这是一个简单的update

简单地说,您需要两个更新语句。但是,您可以将它们包装在同一个update command

db.runCommand({
  update: 'users',
  updates: [
    { q: { name: 'bob' },
      u: { $setOnInsert: { updatedAt: now, updates: 0 }},
      upsert: true },
    { q: { name: 'bob', updatedAt: { $lt: now } },
      u: { $set: { updatedAt: now }, $inc: { updates: 1 } },
      upsert: false },
  ]
})

第一次运行时生成(空集合):

> var now = new Date()
> db.runCommand({   update: 'users',   updates: [     { q: { name: 'bob' },       u: { $setOnInsert: { updatedAt: now, updates: 0 }},       upsert: true },     { q: { name: 'bob', updatedAt: { $lt: now } },       u: { $set: { updatedAt: now }, $inc: { updates: 1 } },       upsert: false },   ] })
{
    "ok" : 1,
    "nModified" : 0,
    "n" : 1,
    "upserted" : [
        {
            "index" : 0,
            "_id" : ObjectId("556f672cd418ed1506eb2ca3")
        }
    ]
}

然后,再次运行时:

> var now = new Date()
> db.runCommand({   update: 'users',   updates: [     { q: { name: 'bob' },       u: { $setOnInsert: { updatedAt: now, updates: 0 }},       upsert: true },     { q: { name: 'bob', updatedAt: { $lt: now } },       u: { $set: { updatedAt: now }, $inc: { updates: 1 } },       upsert: false },   ] })
{ "ok" : 1, "nModified" : 1, "n" : 2 }

请注意,更新命令不是原子的。但是,其他客户端无法将部分创建或更新的用户文档视为第一个更新语句创建完全填充的文档,或者它已经存在(并保持不变)直到第二个完全更新它的声明。

如果用户在两个语句之间同时创建,它甚至是安全的 - 如果需要,它将被透明地更新。