如何在一次查询中根据条件更新/插入多个文档 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
不存在的文档吗?
我想执行 insert
或 update
,如果 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
函数旨在过滤掉相对较少的文档并应用(大部分)持续更新模式,这不是此处所需的操作。
可以通过两步方式轻松完成此操作:
- 将您的更新和可能的插入加载到临时集合中。
- 使用
$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 倍。
我有 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
不存在的文档吗?
我想执行 insert
或 update
,如果 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
函数旨在过滤掉相对较少的文档并应用(大部分)持续更新模式,这不是此处所需的操作。
可以通过两步方式轻松完成此操作:
- 将您的更新和可能的插入加载到临时集合中。
- 使用
$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 倍。