尝试与 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 upsertbulkWrite() 方法中调整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)。 请注意,我在架构中的唯一索引(需要 upsert 正常工作)定义为:

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:批量更新插入,但仅在满足某些条件时才更新记录

Mongoose 中的批量更新

Mongoose:如何从 find() 批量更新修改后的数据

使用 Mongoose 批量删除

Tensorflow细节-P84-梯度下降与批量梯度下降

如何以干净有效的方式在pytorch中获得小批量?