MongoDB - обновление или вставка объекта в массив

У меня есть следующая коллекция

{
    "_id" : ObjectId("57315ba4846dd82425ca2408"),
    "myarray" : [ 
             {
                userId : ObjectId("570ca5e48dbe673802c2d035"),
                point : 5
             },
             {
                userId : ObjectId("613ca5e48dbe673802c2d521"),
                point : 2
             },        
     ]
}

Это мои вопросы

Я хочу нажать в myarray, если userId не существует, его следует добавить в myarray. Если userId существует, он должен быть обновлен до пункта.

Я нашел это

db.collection.update({
  _id : ObjectId("57315ba4846dd82425ca2408"), "myarray.userId" :  ObjectId("570ca5e48dbe673802c2d035")
},{
   $set: {"myarray.$.point": 10}
})

Но если userId не существует, ничего не происходит.

и

db.collection.update({
  _id : ObjectId("57315ba4846dd82425ca2408")
},{
  $push: {"myarray": {userId: ObjectId("570ca5e48dbe673802c2d035"), point: 10}}
})

Но если объект userId уже существует, он снова будет нажимать.

Каков наилучший способ сделать это в MongoDB?

Ответ 1

Попробуй это

db.collection.update(
    { _id : ObjectId("57315ba4846dd82425ca2408")},
    { $pull: {"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")}}
)
db.collection.update(
    { _id : ObjectId("57315ba4846dd82425ca2408")},
    { $push: {"myarray": {
        userId:ObjectId("570ca5e48dbe673802c2d035"),
        point: 10
    }}
)

Ответ 2

Принятый ответ Flying Fisher заключается в том, что существующая запись сначала будет удалена, а затем снова будет нажата.

Более безопасный подход (здравый смысл) состоял бы в том, чтобы попытаться сначала обновить запись, и если это не нашло совпадения, вставьте его так:

// first try to overwrite existing value
var result = db.collection.update(
   {
       _id : ObjectId("57315ba4846dd82425ca2408"),
       "myarray.userId": ObjectId("570ca5e48dbe673802c2d035")
   },
   {
       $set: {"myarray.$.point": {point: 10}}
   }
);
// you probably need to modify the following if-statement to some async callback
// checking depending on your server-side code and mongodb-driver
if(!result.nMatched)
{
    // record not found, so create a new entry
    // this can be done using $addToSet:
    db.collection.update(
        {
            _id: ObjectId("57315ba4846dd82425ca2408")
        },
        {
            $addToSet: {
                myarray: {
                    userId: ObjectId("570ca5e48dbe673802c2d035"),
                    point: 10
                }
            }
        }
    );
    // OR (the equivalent) using $push:
    db.collection.update(
        {
            _id: ObjectId("57315ba4846dd82425ca2408"),
            "myarray.userId": {$ne: ObjectId("570ca5e48dbe673802c2d035"}}
        },
        {
            $push: {
                myarray: {
                    userId: ObjectId("570ca5e48dbe673802c2d035"),
                    point: 10
                }
            }
        }
    );
}

Это также должно дать (здравый смысл, непроверенный) увеличение производительности, если в большинстве случаев запись уже существует, будет выполнен только первый запрос.

Ответ 3

К сожалению, операция "upsert" невозможна для встроенного массива. Операторов просто не существует, так что это невозможно в одном утверждении. Если вы делаете то, что хотите, вы должны выполнить две операции обновления. Кроме того, порядок применения этих двух обновлений важен для получения желаемого результата.

Ответ 5

Когда вы хотите обновить или вставить значение в массив, попробуйте

Объект в дБ

key:name,
key1:name1,
arr:[
    {
        val:1,
        val2:1
    }
]

запрос

var query = {
    $inc:{
        "arr.0.val": 2,
        "arr.0.val2": 2
    }
}
.updateOne( { "key": name }, query, { upsert: true }

key:name,
key1:name1,
arr:[
    {
        val:3,
        val2:3
    }
]

Ответ 6

Я не нашел никаких решений, основанных на одном атомарном запросе. Вместо этого есть 3 способа, основанные на последовательности двух запросов:

  1. всегда $pull (чтобы удалить элемент из массива), затем $push (чтобы добавить обновленный элемент в массив)

    db.collection.update(
                   { _id : ObjectId("57315ba4846dd82425ca2408")},
                   { $pull: {"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")}}
    )
    
    db.collection.update(
                   { _id : ObjectId("57315ba4846dd82425ca2408")},
                   {
                     $push: {
                              "myarray": {
                                          userId:ObjectId("570ca5e48dbe673802c2d035"),
                                          point: 10
                                         }
                             }
                    }
    )
    
  2. попробуйте $set (обновить элемент в массиве, если он существует), затем получите результат и проверьте, успешно ли выполнена операция обновления или требуется ли $push (для вставки элемента)

    var result = db.collection.update(
        {
           _id : ObjectId("57315ba4846dd82425ca2408"),
          "myarray.userId": ObjectId("570ca5e48dbe673802c2d035")
        },
        {
           $set: {"myarray.$.point": {point: 10}}
        }
     );
    
    if(!result.nMatched){
           db.collection.update({_id: ObjectId("57315ba4846dd82425ca2408")},
                                {
                                  $addToSet: {
                                               myarray: {
                                                  userId: ObjectId("570ca5e48dbe673802c2d035"),
                                                  point: 10
                                              }
                                }
           );
    
  3. всегда $addToSet (чтобы добавить элемент, если он не существует), затем всегда $set для обновления элемента в массиве

       db.collection.update({_id: ObjectId("57315ba4846dd82425ca2408")}, myarray: { $not: { $elemMatch: {userId: ObjectId("570ca5e48dbe673802c2d035")} } } },
                            { 
                               $addToSet : {
                                             myarray: {
                                                        userId: ObjectId("570ca5e48dbe673802c2d035"),
                                                        point: 10
                                                       }
                                            }
                             },
                            { multi: false, upsert: false});
    
       db.collection.update({
                              _id: ObjectId("57315ba4846dd82425ca2408"),
                               "myArray.userId": ObjectId("570ca5e48dbe673802c2d035")
                            },
                            { $set : { myArray.$.point: 10 } },
                            { multi: false, upsert: false});
    

1 и 2 пути небезопасны, поэтому необходимо установить транзакцию, чтобы избежать двух одновременных запросов, которые могли бы push создать один и тот же элемент.

3 способ безопасен. $addToSet добавляйте только если элемент не существует, иначе ничего не происходит. В случае двух одновременных запросов только один из них добавляет отсутствующий элемент в массив.