如何在一次查询中根据条件更新/插入多个文档 MongoDB

How to update / insert multiple documents based on condition in one query MongoDB

我有 1 个文档的以下结构:

{
    "uid": uid,
    "at": at,
    "url": url,
    "members": members
}

如果文档已经存在,我想批量创建/更新文档 uid,因为我在一个请求中传递了很多上述对象。

我想要看起来像(python)的东西:

ids = []
for item in items:
  ids.append(item["uid"])
db.collection.update_many({"_id": {"$id": ids}}, {"$set": items}, upsert=True)

但是它会根据正确的 ids 更新正确的文档并插入 uid 不存在的文档吗?

我想执行 insertupdate,如果 object 存在或不存在,它将根据 uid 插入或替换它。

提前致谢

没有直接的方法 upsert 包含内容列表的键列表,例如(这是伪代码,不是有效的 MQL):

keys = ['A','B'];
content = [{key:'A',name:'X',pos:1},{key:'B',name:'Y',pos:2}];
db.collection.updateMany(keys, {$set: content});

updateMany 函数旨在过滤掉相对较少的文档并应用(大部分)持续更新模式,这不是此处所需的操作。

可以通过两步方式轻松完成此操作:

  1. 将您的更新和可能的插入加载到临时集合中。
  2. 使用 $merge 运算符将 material 叠加到目标集合上。

考虑这个现有的目标集合 foo:

db.foo.insert([
    {uid: 0, at: "A", members: [1,2]},
    {uid: 1, at: "B", members: [1,2]},
    {uid: 2, at: "C", members: [1,2]},
    {uid: 3, at: "D", members: [1,2]}
]);

假设我们构建了一个项目数组,类似于 OP 发布的内容:

var items = [
    {uid:1, at:"XX"},   // change
    {uid:3, at:"XX", members:[3,4]},  // change
    {uid:7, at:"Q", members:[3,4]},   // new
    {uid:8, at:"P", members:[3,4]}    // new
];

以下管道会将它们合并到目标集合中:


db.TEMP1.drop(); // OK if does not exist

// Blast items into the TEMP1 collection.  You could use the bulk
// insert functions https://docs.mongodb.com/manual/reference/method/Bulk.insert/ if you prefer,
// or in theory even use mongoimport outside of this script to bulk
// load material into TEMP1.
db.TEMP1.insert(items); 

c=db.TEMP1.aggregate([
    {$project: {_id:false}}, // must exclude _id to prevent conflict with target 'foo'
    {$merge: {
        into: "foo",  // ah HA!  
        on: [ "uid" ],
        whenMatched: "replace",
        whenNotMatched: "insert"
    }}
]);

db.foo.find();
{ "_id" : 0, "uid" : 0, "at" : "A", "members" : [ 1, 2 ] }  // unchanged
{ "_id" : 1, "uid" : 1, "at" : "XX" }     // modified and members gone
{ "_id" : 2, "uid" : 2, "at" : "C", "members" : [ 1, 2 ] }  // unchanged
{ "_id" : 3, "uid" : 3, "at" : "XX", "members" : [ 3, 4 ] }  // modified
{ "_id" : ObjectId("6228be517dec6ed6fa40cbe3"), "uid" : 7, "at" : "Q", "members" : [ 3, 4 ] }   // inserted
{ "_id" : ObjectId("6228be517dec6ed6fa40cbe4"), "uid" : 8, "at" : "P", "members" : [ 3, 4 ] }   // inserted

需要注意的是,唯一索引必须存在于目标集合中的 uid 上,但这可能是您无论如何想要让此类操作合理执行的索引。

这种方法更 client-side 面向数据,即它假设客户端正在构建大而完整的数据结构以覆盖或插入,而不是 update 更“外科手术”,因为它能够有选择地接触尽可能少的区域。

为了进行定量比较,将 200 万个大小与上述大致相同的文档加载到 foo 中。使用批量操作的脚本将 50,000 个项目加载到 TEMP1 中,其中一半匹配 uid(用于更新),另一半没有匹配(用于插入),然后是 运行 18=] 脚本。在 MacBookPro w/16GB RAM 和 SSD 磁盘以及 v4.4.2 服务器上,时间为

1454 millis to insert 50013; 34396 ins/sec
7144 millis to merge; 7000 upd/sec
8598 total millis; 5816 ops/sec

如果在一个简单的循环中完成:

33452 millis for loop update; 1495 upd/sec

因此临时收集和 $merge 方法快了将近 4 倍。