IT

mongodb에서 여러 배열 요소를 업데이트하는 방법

lottoking 2020. 5. 26. 07:59
반응형

mongodb에서 여러 배열 요소를 업데이트하는 방법


요소 배열을 보유한 Mongo 문서가 있습니다.

= XX .handled배열의 모든 객체 속성 을 재설정하고 싶습니다 .profile.

이 문서는 다음과 같은 형식입니다.

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

그래서 나는 다음을 시도했다.

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

그러나 각 문서에서 처음 일치하는 배열 요소 만 업데이트합니다 . ( $-위치 연산자에 대해 정의 된 동작입니다 .)

일치하는 배열 요소를 모두 업데이트하려면 어떻게 합니까?


현재 위치 연산자를 사용하여 배열의 모든 항목을 업데이트 할 수 없습니다. JIRA 참조 http://jira.mongodb.org/browse/SERVER-1243

당신의 문제를 해결하기 위해 :

  • 각 항목을 개별적으로 업데이트하거나 (events.0.handled events.1.handled ...) 또는 ...
  • 문서를 읽고 수동으로 편집 한 후 이전 문서를 대체하여 저장하십시오 ( 원자 업데이트를 확인하려면 "현재 경우 업데이트" 를 확인하십시오).

나를 위해 일한 것은 이것이었다 :

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

나는 mongo 초보자와 JQuery와 친구들에 대해 더 잘 알고 있다고 생각합니다.


으로 MongoDB를 3.6 버전 (MongoDB를 3.5.12에서 개발 지점에서 사용 가능한) 이제 하나의 요청에서 여러 배열 요소를 업데이트 할 수 있습니다.

버전에서 소개 된 필터링 된 위치$[<identifier>] 업데이트 연산자 구문을 사용합니다 .

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

"arrayFilters"에 대한 옵션에 전달로 .update()또는 .updateOne(), .updateMany(), .findOneAndUpdate()또는 .bulkWrite()방법의 지정 조건이 업데이트 문에 주어진 식별자에 일치하는. 주어진 조건과 일치하는 요소가 업데이트됩니다.

것을 주목 "multi"질문의 맥락에서 주어진 여전히이 "여러 요소를 업데이트 할"것이라고 기대에 사용하지만이 아니었고 된 것은 아니다. 여기서는 항상 최신의 API 버전에서 필수 설정으로 지정된 경우와 같이 "여러 문서"에 적용됩니다 .updateMany().

참고 이것은 아이러니하게도, 이것은 .update()메소드 의 "옵션"인수에 지정되어 있기 때문에 일반적으로 모든 최신 릴리스 드라이버 버전과 호환됩니다.

그러나 mongo쉘에서 사실이 아닙니다 . 메소드가 구현 된 방식 ( "철도 적으로 호환성을 위해")에서는 arrayFilters인수가 이전 버전과의 "역 호환성"을 제공하기 위해 옵션을 구문 분석하는 내부 메소드로 인식 및 제거되지 않기 MongoDB 서버 버전 및 "레거시" .update()API 호출 구문

따라서 mongo쉘 또는 기타 "쉘 기반"제품 (특히 Robo 3T) 에서 명령을 사용 하려면 개발 지점 또는 프로덕션 릴리스 3.6 이상에서 최신 버전이 필요합니다.

또한 positional all $[]"여러 배열 요소"를 업데이트하지만 지정된 조건에 적용하지 않고 원하는 조치가있는 배열의 모든 요소에 적용되는 것도 참조하십시오 .

또한 이러한 새로운 위치 연산자가 "중첩 된"배열 구조 (어레이가 다른 배열 내에 있음)에 적용되는 방법에 대해서는 MongoDB 를 사용 하여 중첩 배열 업데이트를 참조하십시오 .

중요 -이전 버전 "에서 업그레이드 된 설치는"gogoDB 기능을 활성화하지 않았을 수 있으며 이로 인해 명령문이 실패 할 수도 있습니다. 인덱스 업그레이드와 같은 세부 정보로 업그레이드 절차를 완료 한 다음 실행해야합니다.

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

또는 설치된 버전에 적용 가능한 상위 버전. "4.0", 현재 버전 4 이상입니다. 이를 통해 새로운 위치 업데이트 연산자 및 기타 기능을 사용할 수있었습니다. 당신은 또한 확인할 수 있습니다 :

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

현재 설정을 되돌리려면


업데이트되지 않은 하위 문서가 여전히 남아있는 문서가 있는지 확인하는 while 루프를 사용하여이 작업을 수행 할 수도 있습니다. 이 방법은 업데이트의 원 자성을 유지합니다 (여기의 다른 많은 솔루션에서는 그렇지 않음).

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

루프가 실행되는 횟수는 컬렉션의 모든 문서에서 profile10과 handled같고 0이 아닌 하위 문서가 발생하는 최대 횟수와 같습니다 . 따라서 컬렉션에 100 개의 문서가 있고 그 중 하나에 일치하는 3 개의 하위 query문서가 있고 다른 모든 문서에 일치하는 하위 문서가 더 적은 경우 루프가 3 번 실행됩니다.

이 방법은이 스크립트가 실행되는 동안 다른 프로세스에 의해 업데이트 될 수있는 다른 데이터를 방해 할 위험을 피합니다. 또한 클라이언트와 서버간에 전송되는 데이터의 양을 최소화합니다.


이것은 실제로 http://jira.mongodb.org/browse/SERVER-1243 의 오랜 문제와 관련 이 있습니다. 실제로 여러 배열이 일치하는 "모든 경우"를 지원하는 명확한 구문에 대한 여러 가지 문제가 있습니다. 녹이다. 실제로 원래 게시물 이후에 구현 된 대량 작업같이이 문제에 대한 솔루션에서 "보조"된 방법이 이미 있습니다.

단일 업데이트 명령문에서 일치하는 단일 배열 요소를 둘 이상 업데이트 할 수 없으므로 "다중"업데이트를 수행하더라도 업데이트 할 수있는 모든 요소는 해당 단일 문서의 각 문서에 대해 배열에서 하나의 계산 된 요소 일뿐입니다 성명서.

현재 가능한 최선의 해결책은 일치하는 모든 문서를 찾아 루프하고 대량 업데이트를 처리하여 최소한 단일 응답으로 단일 작업으로 많은 작업을 보낼 수있게하는 것입니다. 선택적으로 .aggregate()검색 결과에 반환 된 배열 내용을 업데이트 선택 조건과 일치하는 배열 내용으로 줄이는 데 사용할 수 있습니다 .

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$setDifference": [
               { "$map": {
                   "input": "$events",
                   "as": "event",
                   "in": {
                       "$cond": [
                           { "$eq": [ "$$event.handled", 1 ] },
                           "$$el",
                           false
                       ]
                   }
               }},
               [false]
            ]
        }
    }}
]).forEach(function(doc) {
    doc.events.forEach(function(event) {
        bulk.find({ "_id": doc._id, "events.handled": 1  }).updateOne({
            "$set": { "events.$.handled": 0 }
        });
        count++;

        if ( count % 1000 == 0 ) {
            bulk.execute();
            bulk = db.collection.initializeOrderedBulkOp();
        }
    });
});

if ( count % 1000 != 0 )
    bulk.execute();

.aggregate()배열의 각 요소에 대한 모든 컨텐츠의 "고유"식별자는 "고유"요소 자체를 형성하고있을 때 일부가 작동 할 것이다. 이는 일치를 위해 배열을 처리하는 데 사용 작업 에서 반환 된 $setDifference모든 false을 필터링하는 데 사용되는 "set"연산자 때문입니다 $map.

배열 내용에 고유 한 요소가없는 경우 다음을 사용하여 대체 방법을 시도 할 수 있습니다 $redact.

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}
])

"처리 된"필드가 실제로 다른 문서 레벨에 존재하는 필드 인 경우 예상치 않은 결과를 얻을 수 있지만 해당 필드가 하나의 문서 위치에만 나타나고 동등하게 일치하는 것이 좋습니다.

글을 쓰는 현재의 릴리스 (3.1 이후 몽고 DB) $filter는 더 간단한 작업을 할 것입니다 :

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

그리고 지원하는 모든 릴리스 .aggregate()는 다음과 같은 접근 방식을 사용할 수 $unwind있지만 해당 연산자를 사용하면 파이프 라인의 배열 확장으로 인해 가장 효율적인 접근 방식이됩니다.

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "events": { "$push": "$events" }
    }}        
])

MongoDB 버전이 집계 출력의 "커서"를 지원하는 모든 경우에, 이는 접근법을 선택하고 대량 업데이트 명령문을 처리하기 위해 표시된 동일한 코드 블록으로 결과를 반복하는 것입니다. 집계 출력의 대량 작업 및 "커서"는 동일한 버전 (MongoDB 2.6)으로 도입되므로 일반적으로 처리를 위해 함께 작동합니다.

이전 버전에서도 .find()커서를 반환하고 배열 요소가 .update()반복에 일치하는 횟수만큼 명령문 실행을 필터링하는 것이 가장 좋습니다 .

db.collection.find({ "events.handled": 1 }).forEach(function(doc){ 
    doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
        db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
    });
});

If you are aboslutely determined to do "multi" updates or deem that to be ultimately more efficient than processing multiple updates for each matched document, then you can always determine the maximum number of possible array matches and just execute a "multi" update that many times, until basically there are no more documents to update.

A valid approach for MongoDB 2.4 and 2.2 versions could also use .aggregate() to find this value:

var result = db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "count": { "$max": "$count" }
    }}
]);

var max = result.result[0].count;

while ( max-- ) {
    db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}

Whatever the case, there are certain things you do not want to do within the update:

  1. Do not "one shot" update the array: Where if you think it might be more efficient to update the whole array content in code and then just $set the whole array in each document. This might seem faster to process, but there is no guarantee that the array content has not changed since it was read and the update is performed. Though $set is still an atomic operator, it will only update the array with what it "thinks" is the correct data, and thus is likely to overwrite any changes occurring between read and write.

  2. Do not calculate index values to update: Where similar to the "one shot" approach you just work out that position 0 and position 2 ( and so on ) are the elements to update and code these in with and eventual statement like:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    Again the problem here is the "presumption" that those index values found when the document was read are the same index values in th array at the time of update. If new items are added to the array in a way that changes the order then those positions are not longer valid and the wrong items are in fact updated.

So until there is a reasonable syntax determined for allowing multiple matched array elements to be processed in single update statement then the basic approach is to either update each matched array element in an indvidual statement ( ideally in Bulk ) or essentially work out the maximum array elements to update or keep updating until no more modified results are returned. At any rate, you should "always" be processing positional $ updates on the matched array element, even if that is only updating one element per statement.

Bulk Operations are in fact the "generalized" solution to processing any operations that work out to be "multiple operations", and since there are more applications for this than merely updating mutiple array elements with the same value, then it has of course been implemented already, and it is presently the best approach to solve this problem.


I'm amazed this still hasn't been addressed in mongo. Overall mongo doesn't seem to be great when dealing with sub-arrays. You can't count sub-arrays simply for example.

I used Javier's first solution. Read the array into events then loop through and build the set exp:

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

This can be abstracted into a function using a callback for the conditional test


I've been looking for a solution to this using the newest driver for C# 3.6 and here's the fix I eventually settled on. The key here is using "$[]" which according to MongoDB is new as of version 3.6. See https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[] for more information.

Here's the code:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

For more context see my original post here: Remove array element from ALL documents using MongoDB C# driver


I tried the following and its working fine.

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// callback function in case of nodejs


The thread is very old, but I came looking for answer here hence providing new solution.

With MongoDB version 3.6+, it is now possible to use the positional operator to update all items in an array. See official documentation here.

Following query would work for the question asked here. I have also verified with Java-MongoDB driver and it works successfully.

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

Hope this helps someone like me.


Actually, The save command is only on instance of Document class. That have a lot of methods and attribute. So you can use lean() function to reduce work load. Refer here. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Another problem with save function, that will make conflict data in with multi-save at a same time. Model.Update will make data consistently. So to update multi items in array of document. Use your familiar programming language and try something like this, I use mongoose in that:

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})

$[] operator selects all nested array ..You can update all array items with '$[]'

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

Reference


Please be aware that some answers in this thread suggesting use $[] is WRONG.

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

The above code will update "handled" to 0 for all elements in "events" array, regardless of its "profile" value. The query {"events.profile":10} is only to filter the whole document, not the documents in the array. In this situation it is a must to use $[elem] with arrayFilters to specify the condition of array items so Neil Lunn's answer is correct.


I just wanted to add another solution that worked for me and is pretty straightforward. Here it's just an array of tags (strings) so to update a tag called "test" to "changed", just do this:

myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
    myDocuments.update(
        {_id: doc._id, tags: "test"}, 
        {$set:{'tags.$': "changed"}});
    });

참고URL : https://stackoverflow.com/questions/4669178/how-to-update-multiple-array-elements-in-mongodb

반응형