[arrays] 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 …) 또는 …
  • 문서를 읽고 수동으로 편집 한 후 이전 문서를 대체하여 저장하십시오 ( 원자 업데이트를 확인하려면 “현재 경우 업데이트” 를 확인하십시오).

답변

으로 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 } )

현재 설정을 되돌리려면


답변

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

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);
  });

몽고 초보자와 JQuery 및 친구에 익숙한 사람에게는 더 명확하다고 생각합니다.


답변

업데이트되지 않은 하위 문서가 여전히 남아있는 문서가 있는지 확인하는 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을 필터링하는 데 사용되는 “set”연산자 때문입니다 .false$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 }});
    });
});

“다중”업데이트를 수행하기로 결정했거나 일치하는 각 문서에 대해 여러 업데이트를 처리하는 것보다 궁극적으로 더 효율적인 것으로 판단되는 경우 항상 가능한 최대 배열 일치 수를 결정하고 “다중”업데이트 만 실행할 수 있습니다. 기본적으로 업데이트 할 문서가 더 이상 없을 때까지.

MongoDB 2.4 및 2.2 버전에 대한 올바른 접근 방법을 사용 .aggregate()하여이 값을 찾을 수도 있습니다 .

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 })
}

어떤 경우 든 업데이트 내에서 원하지 않는 사항이 있습니다 .

  1. 배열을 “한 번에”업데이트하지 마십시오. 코드에서 전체 배열 내용을 업데이트 한 다음 $set각 문서의 전체 배열 만 업데이트하는 것이 더 효율적일 수 있습니다 . 처리 속도가 더 빠를 수 있지만 배열 내용을 읽은 후 업데이트가 수행 된 이후로 변경되지 않았다는 보장은 없습니다. 비록 $set단지 올바른 데이터 인 “생각”, 따라서, 판독과 기록 사이에서 발생하는 변경을 덮어 가능성 것과 배열을 업데이트 여전히 원자 연산자이다.

  2. 업데이트 할 인덱스 값을 계산하지 마십시오 . “원샷”접근 방식과 유사한 경우 해당 위치 0및 위치 등 2을 업데이트하여 다음과 같은 최종 결과 명령문을 업데이트하고 코딩하는 요소입니다.

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

    여기서도 문제는 문서를 읽을 때 발견 된 색인 값이 업데이트시 배열의 색인 값과 동일한 “가정”입니다. 순서를 변경하는 방식으로 배열에 새 항목을 추가하면 해당 위치가 더 이상 유효하지 않으며 잘못된 항목이 실제로 업데이트됩니다.

따라서 여러 개의 일치하는 배열 요소를 단일 업데이트 명령문에서 처리 할 수 ​​있도록 합리적인 구문이 결정될 때까지 기본 접근 방식은 일치하는 각 배열 요소를 개별 명령문 (이상적으로는 Bulk)으로 업데이트하거나 본질적으로 최대 배열 요소를 해결하는 것입니다. 더 이상 수정 된 결과가 반환되지 않을 때까지 업데이트하거나 계속 업데이트합니다. 어쨌든, 일치하는 배열 요소에 대한 위치$ 업데이트를 “항상”처리해야 합니다. 명령문마다 하나의 요소 만 업데이트하는 경우에도 마찬가지입니다.

대량 작업은 실제로 “다중 작업”으로 작동하는 작업을 처리하기위한 “일반화 된”솔루션이며, 동일한 값으로 여러 배열 요소를 업데이트하는 것보다 더 많은 응용 프로그램이 있으므로 물론 구현되었습니다. 이미이 문제를 해결하는 가장 좋은 방법입니다.


답변

나는 이것이 여전히 몽고에서 다루어지지 않은 것에 놀랐다. 하위 배열을 다룰 때 전반적인 몽고는 좋지 않은 것 같습니다. 예를 들어 하위 배열을 계산할 수 없습니다.

Javier의 첫 번째 솔루션을 사용했습니다. 배열을 이벤트로 읽은 다음 반복하여 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});

조건부 테스트를위한 콜백을 사용하여 함수로 추상화 할 수 있습니다.


답변

나는 C # 3.6의 최신 드라이버를 사용하여 이것에 대한 해결책을 찾고 있었고 결국 해결 된 수정 사항이 있습니다. 여기서 중요한 것은 “$ []”를 사용 하는 것으로, MongoDB에 따르면 버전 3.6부터는 새로운 것입니다. https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up을 참조 하십시오. 자세한 내용은 S [] 를 참조하십시오.

코드는 다음과 같습니다.

{
   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});
}

자세한 내용은 MongoDB C # 드라이버를 사용하여 모든 문서에서 배열 요소 제거를 참조하십시오.