使用mongoose在MongoDB中批量upsert

时间:2014-08-13 11:42:11

标签: javascript node.js mongodb mongoose mongodb-query

有没有选项用mongoose执行批量upserts?所以基本上有一个数组并插入每个元素,如果它不存在或更新它,如果它存在? (我正在使用海关_ids)

当我使用 .insert 时,MongoDB会为重复键返回错误E11000(应该更新)。插入多个新文档可以正常工作:

var Users = self.db.collection('Users');

Users.insert(data, function(err){
            if (err) {
                callback(err);
            }
            else {
                callback(null);
            }
        });

使用 .save 会返回错误,该参数必须是单个文档:

Users.save(data, function(err){
   ...
}

This answer建议没有这样的选择,但是它特定于C#并且已经有3年了。所以我想知道是否有任何选择使用mongoose吗?

谢谢!

7 个答案:

答案 0 :(得分:22)

不在" mongoose"特别地,或者至少还没有写作。从2.6版开始的MongoDB shell实际上使用了"Bulk operations API""引擎盖下的#34;因为它适用于所有一般辅助方法。在它的实现中,它首先尝试执行此操作,如果检测到旧版本服务器,则会出现" fallback"遗留实施。

所有猫鼬方法"目前"使用"遗产"实现或写入关注响应和基本遗留方法。但是,任何给定的mongoose模型都有一个.collection访问器,它基本上可以访问"集合对象"来自底层"节点本机驱动程序"在哪个猫鼬实现自己:

 var mongoose = require('mongoose'),
     Schema = mongoose.Schema;

 mongoose.connect('mongodb://localhost/test');

 var sampleSchema  = new Schema({},{ "strict": false });

 var Sample = mongoose.model( "Sample", sampleSchema, "sample" );

 mongoose.connection.on("open", function(err,conn) { 

    var bulk = Sample.collection.initializeOrderedBulkOp();
    var counter = 0;

    // representing a long loop
    for ( var x = 0; x < 100000; x++ ) {

        bulk.find(/* some search */).upsert().updateOne(
            /* update conditions */
        });
        counter++;

        if ( counter % 1000 == 0 )
            bulk.execute(function(err,result) {             
                bulk = Sample.collection.initializeOrderedBulkOp();
            });
    }

    if ( counter % 1000 != 0 )
        bulk.execute(function(err,result) {
           // maybe do something with result
        });

 });

主要有这样的&#34; mongoose方法&#34;实际上已经意识到实际上还没有建立连接并且#34;队列&#34;直到完成。你是本土的司机&#34;挖掘&#34;没有做出这种区分。

所以你必须要知道连接是以某种方式或形式建立的。但是只要你小心处理你正在做的事情,就可以使用本机驱动程序方法。

答案 1 :(得分:18)

您不需要像@ neil-lunn建议那样管理限制(1000)。 Mongoose已经这样做了。我用他的好答案作为完整的基于Promise的实现的基础。例如:

var Promise = require('bluebird');
var mongoose = require('mongoose');

var Show = mongoose.model('Show', {
  "id": Number,
  "title": String,
  "provider":  {'type':String, 'default':'eztv'}
});

/**
 * Atomic connect Promise - not sure if I need this, might be in mongoose already..
 * @return {Priomise}
 */
function connect(uri, options){
  return new Promise(function(resolve, reject){
    mongoose.connect(uri, options, function(err){
      if (err) return reject(err);
      resolve(mongoose.connection);
    });
  });
}

/**
 * Bulk-upsert an array of records
 * @param  {Array}    records  List of records to update
 * @param  {Model}    Model    Mongoose model to update
 * @param  {Object}   match    Database field to match
 * @return {Promise}  always resolves a BulkWriteResult
 */
function save(records, Model, match){
  match = match || 'id';
  return new Promise(function(resolve, reject){
    var bulk = Model.collection.initializeUnorderedBulkOp();
    records.forEach(function(record){
      var query = {};
      query[match] = record[match];
      bulk.find(query).upsert().updateOne( record );
    });
    bulk.execute(function(err, bulkres){
        if (err) return reject(err);
        resolve(bulkres);
    });
  });
}

/**
 * Map function for EZTV-to-Show
 * @param  {Object} show EZTV show
 * @return {Object}      Mongoose Show object
 */
function mapEZ(show){
  return {
    title: show.title,
    id: Number(show.id),
    provider: 'eztv'
  };
}

// if you are  not using EZTV, put shows in here
var shows = []; // giant array of {id: X, title: "X"}

// var eztv = require('eztv');
// eztv.getShows({}, function(err, shows){
//   if(err) return console.log('EZ Error:', err);

//   var shows = shows.map(mapEZ);
  console.log('found', shows.length, 'shows.');
  connect('mongodb://localhost/tv', {}).then(function(db){
    save(shows, Show).then(function(bulkRes){
      console.log('Bulk complete.', bulkRes);
      db.close();
    }, function(err){
        console.log('Bulk Error:', err);
        db.close();
    });
  }, function(err){
    console.log('DB Error:', err);
  });

// });

这可以在完成连接时关闭连接,如果你关心则显示任何错误,但如果没有则忽略它们(Promises中的错误回调是可选的。)它也非常快。离开这里分享我的发现。例如,如果要将所有eztv节目保存到数据库,可以取消注释eztv的内容。

答案 2 :(得分:3)

我已经发布了一个Mongoose插件,它公开了一个静态upsertMany方法,用promise接口执行批量upsert操作。

使用此插件而不是在底层集合上初始化自己的批量操作,这个插件的一个额外好处是,此插件首先将数据转换为Mongoose模型,然后在upsert之前返回到普通对象。这样可以确保应用Mongoose模式验证,并且数据将减少并适合原始插入。

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

希望它有所帮助!

答案 3 :(得分:3)

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(err => console.error('BULK update error:', err))

https://stackoverflow.com/a/60330161/5318303

答案 4 :(得分:1)

如果您没有在db.collection中看到批量方法,那么您将收到错误信息 xxx变量没有方法:initializeOrderedBulkOp()

尝试更新您的猫鼬版本。显然,较旧的mongoose版本不会通过所有底层的mongo db.collection方法。

npm install mongoose

为我照顾它。

答案 5 :(得分:0)

我最近在将电子商务应用中的产品存储时必须实现这一目标。我的数据库用于超时,因为我必须每4小时插入10000个项目。对我来说,一个选择是在连接到数据库的同时在mongoose中设置socketTimeoutMS和connectTimeoutMS但是它感觉很糟糕,我不想操纵数据库的连接超时默认值。我还看到@neil lunn的解决方案采用了一种简单的同步方法,即在for循环中获取模数。这是我的异步版本,我相信这项工作要好得多

$2, $1$3

答案 6 :(得分:0)

你可以使用猫鼬的 Model.bulkWrite()

const res = await Character.bulkWrite([
  {
    updateOne: {
      filter: { name: 'Will Riker' },
      update: { age: 29 },
      upsert: true
    }
  },
  {
    updateOne: {
      filter: { name: 'Geordi La Forge' },
      update: { age: 29 },
      upsert: true
    }
  }
]);

参考:https://masteringjs.io/tutorials/mongoose/upsert