尝试与 Mongoose 进行批量更新。最干净的方法是啥?
Posted
技术标签:
【中文标题】尝试与 Mongoose 进行批量更新。最干净的方法是啥?【英文标题】:Trying to do a bulk upsert with Mongoose. What's the cleanest way to do this?尝试与 Mongoose 进行批量更新。最干净的方法是什么? 【发布时间】:2017-02-20 16:18:53 【问题描述】:我有一个包含三个字段的文档的集合:first_name、last_name 和 age。我试图弄清楚 Mongoose 中的哪些查询可以用来进行批量 upsert。我的应用程序偶尔会收到具有这三个字段的新对象数组。我希望查询检查文档中是否已经存在名字和姓氏,如果存在 - 如果年龄不同,请更新年龄。否则,如果名字和姓氏不存在,则插入一个新文档。
目前,我只进行导入 - 尚未构建此 upsert 片段的逻辑。
app.post('/users/import', function(req, res)
let data = req.body;
let dataArray = [];
data.forEach(datum =>
dataArray.push(
first: datum.first,
last: datum.last,
age: datum.age
)
)
User.insertMany(dataArray, answer =>
console.log(`Data Inserted:`,answer)
)
`
我的用户模型如下所示:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
first: String,
last: String,
age: Number,
created_at: type: Date, default: Date.now
);
var User = mongoose.model('User', userSchema);
module.exports = User;
【问题讨论】:
您使用的是什么版本的 Mongoose 和 MongoDB? mongo - 3.2.9 mongoose - 4.6.3 这能回答你的问题吗? Bulk upsert in MongoDB using mongoose 【参考方案1】:(mongoose@4.9.1, mongodb@3.4.2)
TL;DR
await GasStation.collection.bulkWrite([ // <<==== use the model name
'updateOne':
'filter': 'id': '<some id>' ,
'update': '$set': /* properties to update */ ,
'upsert': true, // <<==== upsert in every document
,
/* other operations here... */
]);
长篇大论:
在与Mongoose API poor documentation 斗争之后,我解决了bulk upsert 在bulkWrite()
方法中调整updateOne:
操作。
需要考虑的一些未记录的事情:
// suppose:
var GasStation = mongoose.model('gasstation', gasStationsSchema);
var bulkOps = [ ];
// for ( ... each gasStation to upsert ...)
let gasStation = country:'a', localId:'b', xyz:'c' ;
// [populate gasStation as needed]
// Each document should look like this: (note the 'upsert': true)
let upsertDoc =
'updateOne':
'filter': 'country': gasStation.country, 'localId': gasStation.localId ,
'update': gasStation,
'upsert': true
;
bulkOps.push(upsertDoc);
// end for loop
// now bulkWrite (note the use of 'Model.collection')
GasStation.collection.bulkWrite(bulkOps)
.then( bulkWriteOpResult =>
console.log('BULK update OK');
console.log(JSON.stringify(bulkWriteOpResult, null, 2));
)
.catch( err =>
console.log('BULK update error');
console.log(JSON.stringify(err, null, 2));
);
这里的两个关键问题是不完整的 API 文档问题(至少在撰写本文时):
'upsert': true
在每个文档中。这在 Mongoose API() 中没有记录,它通常是指 node-mongodb-native 驱动程序。查看updateOne in this driver,您可以考虑添加'options':'upsert': true
,但是,不……那不行。我还尝试将这两种情况都添加到 bulkWrite(,[options],)
参数中,但也没有任何效果。
GasStation.collection.bulkWrite()
。尽管Mongoose bulkWrite() method 声称它应该被称为Model.bulkWrite()
(在本例中为GasStation.bulkWrite()
),但这将触发MongoError: Unknown modifier: $__
。因此,必须使用Model.collection.bulkWrite()
。
另外,请注意:
updateOne.update
字段中使用 $set
mongo 运算符,因为 mongoose 会在 upsert 的情况下处理它(请参阅 bulkWrite() comments in example)。gasStationsSchema.index( country: 1, localId: 1 , unique: true );
希望对你有帮助。
==> 编辑:(猫鼬 5?)
正如@JustinSmith 所注意到的,Mongoose 添加的$set
运算符似乎不再起作用了。也许是因为 Mongoose 5?
无论如何,明确使用$set
应该这样做:
'update': '$set': gasStation ,
【讨论】:
我从 2020 年开始,Model.bulkWrite(bulkOps)
就是这样做的方式。 Model.collection.bulkWrite()
返回成功但不修改任何数据。
_id 在创建新文档时始终为空。【参考方案2】:
感谢@maganap。我使用his/her answer 并达到以下简洁方法:
await Model.bulkWrite(docs.map(doc => (
updateOne:
filter: id: doc.id,
update: doc,
upsert: true,
)))
或更详细:
const bulkOps = docs.map(doc => (
updateOne:
filter: id: doc.id,
update: doc,
upsert: true,
))
Model.bulkWrite(bulkOps)
.then(console.log.bind(console, 'BULK update OK:', bulkWriteOpResult))
.catch(console.error.bind(console, 'BULK update error:'))
【讨论】:
您能否确认Model.bulkWrite()
在最新的猫鼬版本中正常工作?因为我无法让它在 mongoose@4.9.1 上运行。除了upsert: true
标志之外,我还必须调用MyModel.collection.bulkWrite()
。不过那是很久以前的事了。
不应该是_id
而不是id
吗?【参考方案3】:
我已经为 Mongoose 发布了一个小插件,它公开了一个静态的 upsertMany
方法,以通过一个 Promise 接口执行批量 upsert 操作。这应该提供一种非常干净的方式来使用 Mongoose 进行批量更新,同时保留模式验证等:
MyModel.upsertMany(items, ['matchField', 'other.nestedMatchField']);
你可以在 npm 或 Github 上找到这个插件:
https://github.com/meanie/mongoose-upsert-manyhttps://www.npmjs.com/package/@meanie/mongoose-upsert-many
【讨论】:
【参考方案4】:我在上面尝试了@magnap 的解决方案,发现它覆盖了我只想更新的当前现有文档。它没有更新我在updates.updateOne
中设置的字段,而是选择文档并将其所有字段替换为.update
中指定的字段。
我最终不得不在我的更新方法中使用$set
来解决这个问题。这是我的控制器最终的样子:
const ObjectId = require('mongodb');
exports.bulkUpsert = (req, res, next) =>
const updates = req.body;
const bulkOps = updates.map(update => (
updateOne:
filter: _id: ObjectId(update.id) ,
// Where field is the field you want to update
update: $set: field: update.field ,
upsert: true
));
// where Model is the name of your model
return Model.collection
.bulkWrite(bulkOps)
.then(results => res.json(results))
.catch(err => next(err));
;
这适用于 Mongoose 5.1.2。
【讨论】:
【参考方案5】:希望我的回答HERE 能帮到你。它异步处理电子商务域的批量更新插入
【讨论】:
【参考方案6】:您可以使用array.map
代替for
const result = await Model.bulkWrite(
documents.map(document =>
document =
...document, ...
last_update: Date.now(),
foo: 'bar'
return
updateOne:
filter: document_id: document.document_id, //filter for each item
update:
$set: document,//update whole document
$inc: version: 1//increase version + 1
,
upsert: true //upsert document
));
【讨论】:
虽然此代码可能会解决问题,including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。【参考方案7】:找到官方解决方案:https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/
而且Mongoose也支持同链。
Bulk.find(<query>).upsert().update(<update>);
Bulk.find(<query>).upsert().updateOne(<update>);
Bulk.find(<query>).upsert().replaceOne(<replacement>);
测试它有效:
BulkWriteResult
result:
ok: 1,
writeErrors: [],
writeConcernErrors: [],
insertedIds: [],
nInserted: 0,
nUpserted: 1,
nMatched: 4186,
nModified: 0,
nRemoved: 0,
upserted: [ [Object] ]
【讨论】:
Mongoose 出错:upsert 不是函数【参考方案8】:检查一下,希望对你有帮助 link
link2
我认为你正在寻找
Bulk.find().upsert().update()
你可以用这个
bulk = db.yourCollection.initializeUnorderedBulkOp();
for (<your for statement>)
bulk.find(ID: <your id>, HASH: <your hash>).upsert().update(<your update fields>);
bulk.execute(<your callback>)
如果找到,它将使用 更新该文档
否则,它将创建一个新文档
【讨论】:
我们使用的是 mongoDB 客户端,而不是 mongoose以上是关于尝试与 Mongoose 进行批量更新。最干净的方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
Mongoose:批量更新插入,但仅在满足某些条件时才更新记录