MongoDB: upsert, когда arrayFilters не может найти совпадение - PullRequest
0 голосов
/ 04 августа 2020

У меня есть такая коллекция:

readers

[{
    "id": 123,
    "name": "John Doe",
    "books": [
      {
        "type": "comic",
        "names": [
          "batman",
          "tintin"
        ]
      },
      {
        "type": "drama",
        "names": [
          "hamlet",
          "otelo"
        ]
      }
    ]
 },
 {...}]

Затем я хочу обновить свою коллекцию, добавив в массив names сопоставление type.

Когда существует type, работает отлично, в этом примере romeo and juliet помещается правильно:

db.collection('readers').updateOne(
    { id: 123 },           
    {
      $push: {
        'books.$[element].names': 'romeo and juliet'
      }
    },
    {
      upsert: true,
      arrayFilters: [{ 'element.type': 'drama' }] // type exists
    }
  );

Проблема возникает , когда type не совпадает:

db.collection('readers').updateOne(
    { id: 123 },           
    {
      $push: {
        'books.$[element].names': 'hobbit'
      }
    },
    {
      upsert: true,
      arrayFilters: [{ 'element.type': 'fantasy' }] // new type
    }
  );

Я ожидаю такого результата, но ничего не добавляется:

{
   "id": 123,
   "name": "John Doe",
   "books": [
    {
      "type": "comic",
      "names": [
        "batman",
        "tintin"
      ]
    },
    {
      "type": "drama",
      "names": [
         "hamlet",
         "otelo"
      ]
    },
    {
      "type": "fantasy",
      "names": [
         "hobbit"             
      ]
    }
  ]
}

Что я делаю не так?

Ответы [ 3 ]

2 голосов
/ 07 августа 2020

Вы ожидаете, что upsert создаст недостающую книгу, но это не так, как это работает.

Upsert: если установлено значение true, создает новый документ, если ни один документ не соответствует критериям запроса. Значение по умолчанию - false, при котором новый документ не вставляется, если совпадений не найдено.

Я думаю, вы создали 2 запроса, один для проверки существования книги, а другой для обновления / создания it (либо pu sh, либо вы создаете книгу)

1 голос
/ 10 августа 2020

Как сказал @GwenM, upsert работает на уровне документа, а не на суб-документах. В вашем примере upsert подходит, если нет документов с соответствием id: 123.

Существует проблема с парой запросов проверка-обновление, которая требует транзакции, чтобы гарантировать отсутствие условий гонки.

Я бы порекомендовал использовать 3 обновления atomi c, завернутые в заказанную массовую запись :

var b = db.readers.initializeOrderedBulkOp();

b.find({ id: 123 }).upsert()
 .updateOne({ $setOnInsert: { "name": "John Doe" } });

b.find({ id: 123, "books.type": { $nin: ["fantasy"] } })
 .updateOne({ $push: { "books": { "type": "fantasy" } } });

b.find({ id: 123, "books.type": "fantasy" })
 .updateOne({ $addToSet: { "books.$.names": "hobbit" } });

b.execute();

Первое - это ваш апсерт - он вставляет документ с id:123 если он не существует.

Второй запрос добавляет book с type: "fantasy", если такого типа еще нет.

Последний запрос добавляет «хоббит» к * 1018 книги * array - обратите внимание, что это $ addToSet, а не $ pu sh, чтобы убедиться, что там нет повторяющихся имен.

Вы можете пропустить первый запрос и вообще забыть о upsert, если вы не хотите вставьте новые документы.

0 голосов
/ 10 августа 2020
var id_value = 123
var type_value = 'drama'
var name_add = 'hobbit'
var output = db.readers.find(
    { $and : [
        { id: id_value },       
        {'books.type': type_value } 
    ]}
  );
if(output.count() == 0){
    //type doesn't exists

   db.readers.updateOne(
    { id: id_value },           
    {
      $addToSet: { books : { type : type_value, names : [name_add] }
      }
    },
    {
      upsert: false
    }
  );
}else{ 
// type exists
    db.readers.updateOne(
    { id: id_value },                  
    {
      $addToSet: {
        'books.$[element].names': name_add
      }
    },
    {
      upsert: true,
      arrayFilters: [{ 'element.type': type_value }] 
    }
  );
}

Это могло бы решить проблему.

...