MongoDB原子“ findOrCreate”:findOne,如果不存在则插入,但不更新


114

如标题所示,我想通过_id对文档执行查找(一个),如果不存在,则创建该文档,然后找到它,还是将其创建,并在回调中返回它。

我不想更新它是否存在,就像我读过的findAndModify一样。我在Stackoverflow上看到了许多其他与此相关的问题,但是同样,不想更新任何内容。

我不确定是否通过创建(不存在)实际上是每个人都在谈论的更新,那么令人困惑:(

Answers:


192

从MongoDB 2.4开始,不再需要findOrCreate像原子操作那样依赖唯一索引(或任何其他解决方法)。

这要归功于2.4新增$setOnInsert运算符运算符使您可以指定仅在插入文档时进行的更新。

结合使用该upsert选项,意味着您可以findAndModify用来实现findOrCreate类似原子的操作。

db.collection.findAndModify({
  query: { _id: "some potentially existing id" },
  update: {
    $setOnInsert: { foo: "bar" }
  },
  new: true,   // return new doc if one is upserted
  upsert: true // insert the document if it does not exist
})

因为$setOnInsert仅影响插入的文档,所以如果找到现有文档,则不会进行任何修改。如果不存在任何文档,它将使用指定的_id向上插入一个文档,然后执行仅插入集。在两种情况下,都将返回文档。


3
@Gank如果您对节点使用mongodb本机驱动程序,则语法将更像collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback)。请参阅文档
Numbers1311407 2014年

7
有没有办法知道文档是否已更新或插入?
厄运

3
如果你想检查是否上述(查询db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})插入(UPSERT)编一个文件,你应该考虑使用db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true})。如果文档已{"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}插入,{"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}则返回该文档。
КонстантинВан

8
似乎已过时findOneAndUpdatefindOneAndReplacefindOneAndDelete
Ravshan Samandarov

5
不过,这里需要小心。仅当findAndModify / findOneAndUpdate / updateOne的选择器通过_id唯一标识一个文档时,此方法才有效。否则,upsert在服务器上分为查询和更新/插入。更新仍然是原子的。但是查询和更新不会一起自动执行。
Anon

14

驱动程序版本> 2

采用最新的驱动程序(> 2版),您将使用findOneAndUpdate作为findAndModify被废弃。这种新方法需要3个参数的filter,该update对象(其中包含您的默认属性,应插入一个新的对象),并options在那里你必须指定更新插入操作。

使用promise语法,它看起来像这样:

const result = await collection.findOneAndUpdate(
  { _id: new ObjectId(id) },
  {
    $setOnInsert: { foo: "bar" },
  },
  {
    returnOriginal: false,
    upsert: true,
  }
);

const newOrUpdatedDocument = result.value;

谢谢你,returnOriginal应该是returnNewDocument我想- docs.mongodb.com/manual/reference/method/...
多米尼克

没关系,节点驱动程序采用不同的方式实现,您的答案是正确的
Dominic

6

它有点脏,但是您可以将其插入。

确保键上具有唯一索引(如果使用_id没关系,那么它已经是唯一的)。

这样,如果元素已经存在,它将返回一个您可以捕获的异常。

如果不存在,将插入新文档。

更新: MongoDB文档中对此技术的详细说明


好的,那是个好主意,但是对于预先存在的值,它将返回错误,但不会返回值本身,对吗?
Discipol

3
实际上,这是建议的隔离操作序列解决方案之一(大概找不到,然后找到即可创建)docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
number1311407 2013年

@Discipol如果要执行一组原子操作,则应首先锁定文档,然后对其进行修改,最后释放它。这将需要更多查询,但是您可以针对最佳情况进行优化,并且大多数情况下仅执行1-2个查询。参见:docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco

1

这是我所做的(Ruby MongoDB驱动程序):

$db[:tags].update_one({:tag => 'flat'}, {'$set' => {:tag => 'earth' }}, { :upsert => true })}

如果存在,它将对其进行更新;如果不存在,则将其插入。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.