MongoDB- Вставить, если он не существует, иначе пропустить

Можно ли вставить в Mongo с условием;

//Pseudo code

Bulk Insert Item :

If Key exists
    Skip, don't throw error
If key does not exist
    Add item

Если я делаю отдельные вставки, он может вернуть ошибку или вставить в коллекцию, но возможно ли это в bulk?

Ответ 1

У вас есть два реальных выбора здесь, в зависимости от того, как вы хотите обрабатывать вещи:

  • Используйте upsert функциональность MongoDB, чтобы существенно "искать", если данные ключа существуют. Если нет, то вы передаете только данные $setOnInsert, и это ничего не трогает.

  • Используйте операции "UnOrdered" в Bulk. Вся серия обновлений будет продолжена, даже если будет возвращена ошибка, но отчет об ошибках будет таким, и все, что не является ошибкой, будет выполнено.

Весь пример:

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

var testSchema = new Schema({
  "_id": Number,
  "name": String
},{ "_id": false });

var Test = mongoose.model('Test',testSchema,'test');

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

var data = [
  { "_id": 1, "name": "One" },
  { "_id": 1, "name": "Another" },
  { "_id": 2, "name": "Two" }
];

async.series(
  [
    // Start fresh
    function(callback) {
      Test.remove({},callback);
    },

    // Ordered will fail on error. Upserts never fail!
    function(callback) {
      var bulk = Test.collection.initializeOrderedBulkOp();
      data.forEach(function(item) {
        bulk.find({ "_id": item._id }).upsert().updateOne({
          "$setOnInsert": { "name": item.name }
        });
      });
      bulk.execute(callback);
    },

    // All as expected
    function(callback) {
      Test.find().exec(function(err,docs) {
        console.log(docs)
        callback(err);
      });
    },


    // Start again
    function(callback) {
      Test.remove({},callback);
    },

    // Unordered will just continue on error and record an error
    function(callback) {
      var bulk = Test.collection.initializeUnorderedBulkOp();
      data.forEach(function(item) {
        bulk.insert(item);
      });
      bulk.execute(function(err,result) {
        callback(); // so what! Could not care about errors
      });
    },


    // Still processed the whole batch
    function(callback) {
      Test.find().exec(function(err,docs) {
        console.log(docs)
        callback(err);
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Обратите внимание, что "измененное действие" в текущих драйверах заключается в том, что ответ на результат .execute() будет возвращать объект ошибки, который должен быть выброшен, где предыдущие выпуски не делали этого с "Un-ordered" операций.

Это делает необходимым, чтобы ваш код никогда не полагался на возвращаемый err, и вы должны вставлять обратно возвращенный result для полной классификации ошибок.

Тем не менее, когда неупорядоченный, партия продолжается до конца, независимо от количества ошибок. Вещи, которые не являются ошибкой, будут выполняться как обычно.

Это действительно сводится к тому, что "важна последовательность". Если это так, то вам нужны операции "Заказ", и вы можете избежать дублирования ключей, используя "upserts". В противном случае используйте "неупорядоченные", но имейте в виду, что возвращается ошибка и что они на самом деле означают.

Кроме того, при использовании .collection, чтобы получить базовый объект коллекции из базового драйвера, чтобы активировать операции "Массовые", всегда убедитесь, что сначала был вызван метод "some" mongoose.

Без этого нет гарантированного подключения к базе данных с помощью собственных методов драйвера, так как она обрабатывается для методов mongoose, поэтому операция не будет выполнена из-за отсутствия соединения.

Альтернативой "стрельбе" методу mongoose во-первых, является объединение вашей логики приложения в прослушиватель событий для соединения:

mongoose.connection.on("open",function(err) {
    // app logic in here
})

Ответ 2

Как уже было сказано, "вставить, если он еще не существует" можно с помощью команды update с опцией upsert установленной в true. Вот как это сделать с помощью драйвера 3.x node.js:

let ops = [];
ops.push({ updateOne: { filter: {key:"value1"}, update: {} }, { upsert:true } });
ops.push({ updateOne: { filter: {key:"value2"}, update: { $set:{/*...*/} } }, { upsert:true } });
ops.push({ updateOne: { filter: {key:"value3"}, update: { { $setOnInsert:{/*...*/} } } }, { upsert:true } });
// < add more ops here >
await db.collection("my-collection").bulkWrite(ops, {ordered:false});

Если filter возвращает ноль результатов, новый документ будет создан с использованием условий фильтра и обновлений $set (если есть). Если вы используете $setOnInsert, то обновления применяются только к новым документам.

Опубликовать этот пример, потому что это было бы удобно для моей ситуации. Больше информации в документации по db.collection.bulkWrite.

Ответ 3

Если я сделаю 10 обновлений с использованием bulkWrite([{... }]), например, обновление 6 завершится неудачно, как я буду действовать? Атомно?