MongoDB-문서 배열의 객체 업데이트 (중복 업데이트)


204

다음과 같은 컬렉션이 있다고 가정 해 보겠습니다.

{
    "_id" : ObjectId("4faaba123412d654fe83hg876"),
    "user_id" : 123456,
    "total" : 100,
    "items" : [
            {
                    "item_name" : "my_item_one",
                    "price" : 20
            },
            {
                    "item_name" : "my_item_two",
                    "price" : 50
            },
            {
                    "item_name" : "my_item_three",
                    "price" : 30
            }
    ]
}

1- "item_name": "my_item_two"의 가격을 높이고 싶지 않은 경우 "items"배열에 추가해야합니다.

2-두 필드를 동시에 업데이트하려면 어떻게해야합니까? 예를 들어, "my_item_three"의 가격을 높이고 동시에 "total"(동일한 값)을 늘리십시오.

MongoDB 쪽에서이 작업을 선호합니다. 그렇지 않으면 클라이언트 쪽 (Python)에 문서를로드하고 업데이트 된 문서를 구성하고 MongoDB의 기존 문서로 바꿔야합니다.

업데이트 객체가 존재 하는 경우 이것이 시도하고 잘 작동합니다 .

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})

그러나 키가 존재하지 않으면 아무것도하지 않습니다. 또한 중첩 된 개체 만 업데이트합니다. 이 명령을 사용하여 "전체"필드도 업데이트 할 수있는 방법이 없습니다.


1
나는 평가를 사용하여 많은 고통을 겪고있는 것을 제외하고는 몽고에서 이것을 할 수 없다고 생각합니다. 몽고는 데이터 운영이 매우 제한적입니다.
Antti Haapala

3
@Haapala : mongodb는 $ inc를 가지고 upsert로 업데이트합니다
jdi

1
@jdi 네, 그렇습니다. 그러나 여기에는별로 도움이되지 않지만 필요한 것은 조건부로 여러 $ incs이며 항목이 존재하지 않으면 $ push가 필요합니다.
Antti Haapala

답변:


237

1 번 문제는 두 부분으로 나누겠습니다. 먼저 "items.item_name"이 "my_item_two"인 문서를 증가시킵니다. 이를 위해서는 위치 "$"연산자를 사용해야합니다. 다음과 같은 것 :

 db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

이렇게하면 배열의 첫 번째로 일치하는 하위 문서 만 증가하므로 배열에 "item_name"이 "my_item_two"인 다른 문서가 있으면 증가하지 않습니다. 그러나 이것은 당신이 원하는 것일 수 있습니다.

두 번째 부분은 더 까다 롭습니다. 다음과 같이 "my_item_two"없이 새 항목을 배열로 푸시 할 수 있습니다.

 db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

2 번 문제는 답이 더 쉽습니다. "my_item_three"가 포함 된 문서에서 item_three의 총 가격과 가격을 높이려면 여러 필드에서 동시에 $ inc 연산자를 사용할 수 있습니다. 다음과 같은 것 :

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);

답장을 보내 주셔서 감사합니다. Question # 2에 대한 답은 완벽하고 잘 작동합니다. :) Question # 1의 경우 문제는 "items.item_name"이 아니라 "user_id"로 문서를 검색해야한다는 것입니다. 이 경우 검색 쿼리에서 배열을 지정하지 않았으므로 위치 연산자를 사용할 수 없습니다. 또한 두 부분을 모두 한 번에 완료하고 싶습니다. (가능한 경우)
Majid

좋은 예입니다. 나는 내 대답의 링크 에서이 방향을 분명히하는 것처럼 느꼈지만, 그가 찾고있는 것이 아니라는 저항을 계속 얻었습니다. OP가 여러 $ inc를 수행하는 방법에 대한 예를보기 위해 필요한 것 같아요.
jdi

질문 # 1의 경우, 각 업데이트의 쿼리 비트에 user_id를 추가하여 두 부분으로 계속 할 수 있습니다 (그에 따라 답변을 수정하겠습니다). 한 번에 할 수 있는지에 대해 더 많이 생각해야합니다.
matulef

6
업데이트 방법의 3 번째 및 4 번째 매개 변수는 무엇입니까? 그리고 왜?
skmahawar

5
3 번째 및 4 번째 매개 변수 인 docs.mongodb.com/manual/reference/method/db.collection.update에 관한 @skmahawar 는 이러한 옵션이 각각 "upsert"및 "multi"에 대한 것임을 나타냅니다. upsert의 경우 true로 설정하면 쿼리 기준과 일치하는 문서가 없을 때 새 문서를 작성합니다. 기본값은 false이며 일치하는 항목이 없으면 새 문서를 삽입하지 않습니다. "multi의 경우 true로 설정하면 쿼리 기준에 맞는 여러 문서가 업데이트됩니다. false로 설정하면 하나의 문서가 업데이트됩니다. 기본값은 거짓.
마이크 스트랜드

24

단일 쿼리에서는이를 수행 할 방법이 없습니다. 첫 번째 쿼리에서 문서를 검색해야합니다.

문서가 존재하는 경우 :

db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

그밖에

db.bar.update( {user_id : 123456 } , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

조건을 추가 할 필요가 없습니다 {$ne : "my_item_two" }.

또한 다중 스레드 환경에서는 한 번에 하나의 스레드 만 두 번째 (문서를 찾을 수없는 경우 삽입 케이스)를 실행할 수 있도록주의해야합니다. 그렇지 않으면 중복 된 삽입 문서가 삽입됩니다.


1
아니요, 단일 쿼리로이 작업을 수행 할 수있는 방법이 있습니다. 위 참조
Martijn Scheffer

1
@MartijnScheffer,이 스레드의 어느 곳에서나 단일 쿼리로 이것을 수행하는 방법을 보지 못했습니다. "답변"조차도이 답변과 같은 두 개의 쿼리를 사용하고 있습니다. 뭔가 빠졌습니까? 또한 멀티 스레딩 문제를 방지하기 위해 하나의 쿼리로이 작업을 수행 할 수있는 방법이 있기를 바랍니다.
dferraro

@ Udit, 조건 내에서 items. $. price를 어떻게 사용할 수 있습니까? "가격이 0보다 큰 경우에만 증가"와 같은 조건을 추가하려고하면 작동하지 않습니다. 기본적으로 아무것도 업데이트되지 않습니다. 어디서나 사용할 수 있습니까, 아니면 뭔가 빠졌습니까? items. $. price : {$ gt : 0}
dferraro

뭔가 사라졌다 :)
Martijn Scheffer

3

우리가 사용할 수있는 $set객체가 값을 갱신 신청 안에 중첩 된 배열을 업데이트 연산자

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.