source

MongoDB: 하위 문서 덮어쓰기

factcode 2023. 5. 29. 11:12
반응형

MongoDB: 하위 문서 덮어쓰기

고유 색인이 있는 그런 문서가 있습니다.bars.name:

{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }

하위 문서를 업데이트하고 싶습니다.{ name: 'foo', 'bars.name': 'qux' }그리고.$set: { 'bars.$.somefield': 2 }또는 를 사용하여 새 하위 항목을 만듭니다.{ name: 'qux', somefield: 2 }아래{ name: 'foo' }.

업서트가 포함된 단일 쿼리를 사용하여 이 작업을 수행할 수 있습니까? 아니면 두 개의 쿼리를 별도로 발행해야 합니까?

관련 : 임베디드 문서에 'upsert' (서브 문서 식별자를 키로 하는 스키마 변경을 제안합니다만, 이것은 2년 전 것이고 지금은 더 나은 해결책이 있는지 궁금합니다.)

아니요, 이에 대한 더 나은 해결책은 없습니다. 그래서 아마도 설명이 필요할 것입니다.

다음과 같은 구조를 가진 문서가 있다고 가정합니다.

{ 
  "name": "foo", 
  "bars": [{ 
       "name": "qux", 
       "somefield": 1 
  }] 
}

만약 당신이 이런 업데이트를 한다면,

db.foo.update(
    { "name": "foo", "bars.name": "qux" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

그럼 일치하는 문서를 찾았으니 괜찮습니다.그러나 "bars.name "의 값을 변경하는 경우:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

그러면 당신은 실패할 것입니다.여기서 유일하게 달라진 점은 MongoDB 2.6 이상에서는 오류가 조금 더 간결하다는 것입니다.

WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 16836,
        "errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield"
    }
})

그것이 어떤 면에서는 더 낫지만, 어쨌든 당신은 정말로 "업그레이드"하고 싶지 않습니다.현재 "이름"이 존재하지 않는 배열에 요소를 추가합니다.

따라서 "upert" 플래그가 없는 업데이트 시도의 "결과"를 사용하여 영향을 받은 문서가 있는지 확인하십시오.

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } }
)

이에 대한 대응:

WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })

그래서 수정된 문서들이0그러면 다음 업데이트를 실행할 수 있습니다.

db.foo.update(
    { "name": "foo" },
    { "$push": { "bars": {
        "name": "xyz",
        "somefield": 2
    }}
)

당신이 정확히 원하는 것을 할 수 있는 다른 방법은 정말 없습니다.어레이에 대한 추가는 엄격하게 "세트" 유형의 작업이 아니므로 사용할 수 없습니다.$addToSet업데이트 요청을 "업데이트"할 수 있도록 "업데이트 업데이트" 기능과 결합됩니다.

이 경우 결과를 확인하거나, 그렇지 않으면 전체 문서를 읽고 코드에 새 배열 요소를 업데이트할지 또는 삽입할지 여부를 확인하는 것을 수락해야 할 것 같습니다.

스키마를 조금만 변경하고 그런 구조를 갖는 것을 개의치 않는다면:

{ "name": "foo", "bars": { "qux": { "somefield": 1 },
                           "xyz": { "somefield": 2 },
                  }
}

한 번에 작업을 수행할 수 있습니다.완전성을 위해 내장된 문서에서 '업서트' 반복

동일한 기능을 찾다가 버전 4.2 이상에서 MongoDB가 Aggregation Pipeline을 사용한 업데이트라는 새로운 기능을 제공한다는 것을 알게 되었습니다.
이 기능을 다른 기술과 함께 사용하면 단일 질의로 하위 문서 작업을 수행할 수 있습니다.

매우 장황한 질문이지만, 하위 컬렉션에 너무 많은 레코드가 없을 것이라는 것을 알고 있다면 실행 가능하다고 생각합니다.다음은 이를 달성하는 방법에 대한 예입니다.

const documentQuery = { _id: '123' }
const subDocumentToUpsert = { name: 'xyz', id: '1' }

collection.update(documentQuery, [
    {
        $set: {
            sub_documents: {
                $cond: {
                    if: { $not: ['$sub_documents'] },
                    then: [subDocumentToUpsert],
                    else: {
                        $cond: {
                            if: { $in: [subDocumentToUpsert.id, '$sub_documents.id'] },
                            then: {
                                $map: {
                                    input: '$sub_documents',
                                    as: 'sub_document',
                                    in: {
                                        $cond: {
                                            if: { $eq: ['$$sub_document.id', subDocumentToUpsert.id] },
                                            then: subDocumentToUpsert,
                                            else: '$$sub_document',
                                        },
                                    },
                                },
                            },
                            else: { $concatArrays: ['$sub_documents', [subDocumentToUpsert]] },
                        },
                    },
                },
            },
        },
    },
])

두 개의 쿼리에서 수행할 수 있는 방법이 있습니다. 하지만 계속해서 작동할 것입니다.bulkWrite.

이것은 제 경우 배치할 수 없는 것이 가장 큰 중단이기 때문에 관련이 있습니다.이 솔루션을 사용하면 첫 번째 쿼리 결과를 수집할 필요가 없으므로 필요한 경우 대량 작업을 수행할 수 있습니다.

다음은 예제에 대해 실행할 두 개의 연속 쿼리입니다.

// Update subdocument if existing
collection.updateMany({
    name: 'foo', 'bars.name': 'qux' 
}, {
    $set: { 
        'bars.$.somefield': 2 
    }
})
// Insert subdocument otherwise
collection.updateMany({
    name: 'foo', $not: {'bars.name': 'qux' }
}, {
    $push: { 
        bars: {
            somefield: 2, name: 'qux'
        }
    }
})

또한 여러 응용프로그램이 동시에 데이터베이스에 쓰는 경우 손상된 데이터/레이스 상태가 발생하지 않는다는 추가적인 이점이 있습니다.은 두 로 끝날 위험을 감수하지 않을 것입니다bars: {somefield: 2, name: 'qux'}두 응용프로그램이 동시에 동일한 쿼리를 실행하는 경우 문서의 하위 문서를 참조합니다.

비슷한 작업을 하려고 했습니다. 문서가 없으면 문서를 작성하고, 하위 문서가 없으면 배열에 하위 문서를 추가하거나, 하위 문서가 이미 배열에 있으면 하위 문서의 카운트 필드를 증분하려고 했습니다.이러한 답변과 다른 답변을 바탕으로 스프링 부트 MongoTemplate를 사용하여 다음과 같이 생각해 냈습니다.저는 다른 프로세스가 동시에 요소를 업데이트하는 경우 중복되지 않고 원하는 결과를 얻을 수 있다는 점에서 이것이 원자적이라고 생각합니다.안타깝게도 데이터베이스 트랜잭션은 3개가 필요합니다. 더 효율적인 방법이 있는지 모르겠습니다.


@Document(collection = "dealerships")
public class Dealership {

    public static class Car {
        String carBrand;
        int count;
        public Car(Sstring brand) {
            this.carBrand = brand;
            count = 1;
        }
    }

    String id;
    List<Car> carsOnLot = new ArrayList<>();    //important - create an empty array when inserting new document

    //Constructor, getters, setters...
}

//First, let's try inserting the a new Dealership document, if that Dealership already exists this will fail
try {
    mongoTemplate.insert(new Dealership("Annapolis"));
} catch (org.springframework.dao.DuplicateKeyException dex) {            
    System.out.println("Annapolis dealer exists!");
}

//----- At this point the Annapolis dealer document exists, but we don't know if our Car is in the carsOnLot array ------------

//This is the query and update for adding to the array "carsOnLot", if the particular car is not already listed
Query addCarQuery = new Query().addCriteria(Criteria.where("id").is("Annapolis"));
addCarQuery.addCriteria(Criteria.where("carsOnLot.carBrand").ne("Audi"));

Update addCarUpdate = new Update();
addCarUpdate.addToSet("carsOnLot", new Car("Audi"));    //this will not duplicate an existing element


//Let's try adding this car brand to the array
UpdateResult ur = mongoTemplate.updateFirst(addCarQuery, addCarUpdate, Dealership.class);
if (ur.getModifiedCount() == 1)  // we added it - our job is done
    return;
else   
    System.out.println("Already Audis on the lot!");


//------ At this point we already have an entry for our brand, so let's increment the count

//This is the query for incrementing the count of cars
Query updateQuery = new Query().addCriteria(Criteria.where("id").is("Annapolis"));
updateQuery.addCriteria(Criteria.where("carsOnLot").elemMatch(Criteria.where("carBrand").is("Audi")));

Update updateUpdate = new Update().inc("carsOnLot.$.count", 1); //increment the count for Audis by 1

 ur = mongoTemplate.updateFirst(updateQuery, updateUpdate, Dealership.class);
 if (ur.getModifiedCount() == 1)    // we incremented it - our job is done
     return;

// Log a failure, something isn't right

언급URL : https://stackoverflow.com/questions/23470658/mongodb-upsert-sub-document

반응형