尝试使用Mongoose进行批量upsert。最干净的方法是什么?

时间:2016-10-12 00:43:13

标签: node.js mongodb mongoose

我有一个包含三个字段的文档的集合: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;

8 个答案:

答案 0 :(得分:19)

(mongoose@4.9.1,mongodb@3.4.2)

在与Mongoose API poor documentation挣扎之后,我解决了updateOne:{}方法中批量upsert 调整bulkWrite()操作。

要考虑的几个未记录的事项:

// 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()

另外,请注意:

  • 您不需要在$set字段中使用updateOne.update mongo运算符,因为mongoose会在upsert处理它(请参阅bulkWrite() comments in example)。
  • 请注意,架构中的唯一索引(upsert正常工作所需)定义为:

gasStationsSchema.index({ country: 1, localId: 1 }, { unique: true });

希望它有所帮助。

<强> ==&GT;编辑:(猫鼬5?)

正如@JustinSmith所注意到的那样,Mongoose添加的$set运算符似乎不再起作用了。也许是因为Mongoose 5?

在任何情况下,明确使用$set都应该:

'update': { '$set': gasStation },

答案 1 :(得分:3)

感谢@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(bulkWriteOpResult => console.log('BULK update OK:', bulkWriteOpResult))
        .catch(console.error.bind(console, 'BULK update error:')

答案 2 :(得分:1)

我发布了一个Mongoose的小插件,它公开了一个静态upsertMany方法,用promise接口执行批量upsert操作。这应该提供一种非常干净的方式来使用Mongoose进行批量upsert,同时保留模式验证等:

MyModel.upsertMany(items, ['matchField', 'other.nestedMatchField']);

你可以在npm或Github上找到这个插件:

https://github.com/meanie/mongoose-upsert-many https://www.npmjs.com/package/@meanie/mongoose-upsert-many

答案 3 :(得分:1)

我在上面尝试了@ magnap的解决方案,发现它覆盖了我想要更新的当前现有文档。它不是更新我在.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)); }; 来解决此问题。这是我的控制器最终看起来像:

{{1}}

这适用于Mongoose 5.1.2。

答案 4 :(得分:0)

希望我的回答HERE可以帮助你。它以异步方式处理电子商务域的批量upsert

答案 5 :(得分:0)

找到了以下官方解决方案:https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/

猫鼬也支持相同的链。

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] ] } }

答案 6 :(得分:0)

您可以使用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
          }
        }
      }
    ));

答案 7 :(得分:-2)

检查一下,我希望这对你有帮助 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>)
  • 如果找到,则会使用{}
  • 更新该文档
  • 否则,它将创建一个新文档