MongoDB에서 배열 요소 조건부 삽입·업데이트 처리하기
배열 내부에 특정 조건을 만족하는 요소가 없으면 새로 삽입하고, 있으면 기존 값을 업데이트해야 하는 상황이라면 MongoDB의 bulkWrite() 메서드를 활용할 수 있습니다. 이 메서드는 여러 개의 쓰기 작업을 하나의 요청으로 묶어 한 번에 처리할 수 있어, 조건에 따라 분기되는 삽입/업데이트 로직을 구현하기에 적합합니다.
아래 예제에서는 학생 성적 정보를 담은 배열을 대상으로, 여러 조건(Marks2, Marks3)을 조합해 요소의 존재 여부를 판단한 뒤 삽입 또는 업데이트를 수행하는 과정을 단계별로 살펴보겠습니다.
1단계: 샘플 컬렉션 생성
먼저 문서 하나를 포함하는 컬렉션을 생성합니다.
> db.demo105.insertOne( { _id:'101', Name:'Chris', Details:[{ Marks1:60, Marks2:70, Marks3:70 }, { Marks1:70, Marks2:70, Marks3:90 }] } );
{ "acknowledged" : true, "insertedId" : "101" }
2단계: 초기 데이터 확인
find() 메서드를 사용해 컬렉션의 모든 문서를 조회합니다.
> db.demo105.find().pretty();
위 명령은 다음과 같은 결과를 출력합니다.
{
"_id" : "101",
"Name" : "Chris",
"Details" : [
{
"Marks1" : 60,
"Marks2" : 70,
"Marks3" : 70
},
{
"Marks1" : 70,
"Marks2" : 70,
"Marks3" : 90
}
]
}
3단계: 조건부 삽입/업데이트 쿼리 실행
다음은 요소가 존재하지 않으면 배열에 삽입하고, 존재하면 여러 조건을 기반으로 업데이트하는 bulkWrite() 쿼리입니다.
> db.demo105.bulkWrite([
... { "updateOne": {
... "filter": {
... "_id": "101",
... "Details": {
... "$elemMatch": { Marks2: 70, Marks3: 70 }
... }
... },
... "update": {
... "$set": { "Details.$.Marks3": 96 }
... }
... }},
... { "updateOne": {
... "filter": {
... "_id": "101",
... "Details": {
... "$not": {
... "$elemMatch": { Marks2: 70, Marks3: 70 }
... }
... }
... },
... "update": {
... "$push": { "Details": { Marks1: 94, Marks2: 97,Marks3:99} }
... }
... }},
... { "updateOne": {
... "filter": { "_id": 101 },
... "update": {
... "$setOnInsert": {
... "Details": [
... { Marks1: 34, Marks2: 67,Marks3:87 }
... ]
... }
... },
... "upsert": true
... }}
... ])
{
"acknowledged" : true,
"deletedCount" : 0,
"insertedCount" : 0,
"matchedCount" : 2,
"upsertedCount" : 1,
"insertedIds" : {
},
"upsertedIds" : {
"2" : 101
}
}
이 쿼리는 세 가지 작업으로 구성됩니다.
- 첫 번째 updateOne:
$elemMatch를 사용해Marks2: 70과Marks3: 70조건을 동시에 만족하는 요소가 배열에 존재하면, 위치 연산자($)를 통해 해당 요소의Marks3값을 96으로 변경합니다. - 두 번째 updateOne:
$not과$elemMatch를 조합해 위 조건을 만족하는 요소가 존재하지 않는 경우에만$push로 새로운 성적 객체를 배열 끝에 추가합니다. - 세 번째 updateOne:
upsert: true와$setOnInsert를 함께 사용해,_id: 101문서 자체가 존재하지 않을 때만 새 문서를 생성하며 초기 배열을 설정합니다.
4단계: 최종 결과 확인
다시 find() 메서드로 전체 문서를 조회해 봅니다.
> db.demo105.find().pretty();
첫 번째 문서에서는 조건(Marks2: 70, Marks3: 70)에 일치하는 요소가 발견되어 Marks3 값이 96으로 업데이트되었고, 이후에는 일치하는 요소가 더 이상 없어 새 요소가 $push로 추가된 것을 확인할 수 있습니다. 또한 문자열 "101"과 숫자 101은 서로 다른 _id로 취급되기 때문에, 세 번째 upsert 작업에 의해 숫자형 _id를 가진 새 문서도 함께 생성되었습니다.
{
"_id" : "101",
"Name" : "Chris",
"Details" : [
{
"Marks1" : 60,
"Marks2" : 70,
"Marks3" : 96
},
{
"Marks1" : 70,
"Marks2" : 70,
"Marks3" : 90
},
{
"Marks1" : 94,
"Marks2" : 97,
"Marks3" : 99
}
]
}
{
"_id" : 101,
"Details" : [
{
"Marks1" : 34,
"Marks2" : 67,
"Marks3" : 87
}
]
}
정리
이처럼 bulkWrite()를 활용하면 $elemMatch로 요소의 존재 여부를 검사해 업데이트하고, $not 조건으로 존재하지 않을 때만 $push하며, upsert로 문서 자체의 부재까지 보완하는 패턴을 하나의 배치로 처리할 수 있습니다. 다만 위 예제에서 확인할 수 있듯이 _id의 타입(문자열 vs 숫자)이 다르면 별개의 문서로 인식되므로, 실무에서는 _id 타입의 일관성을 유지하는 것이 매우 중요합니다.