MongoDB 배열 데이터를 개별 레코드로 분리하기
MongoDB를 사용하다 보면 하나의 문서(document)에 배열 형태로 저장된 값을 각각 독립적인 문서로 분리해야 하는 경우가 자주 발생합니다. 예를 들어, 한 사용자 문서에 여러 이름이 배열로 저장되어 있을 때, 이를 이름별 개별 레코드로 변환하고 싶다면 애그리게이션 파이프라인의 $unwind와 $out 연산자를 활용하면 됩니다.
핵심 단계는 다음과 같습니다.
- aggregate(): 애그리게이션 파이프라인을 실행합니다.
- $unwind: 배열 필드를 요소별로 분해하여 각 요소마다 하나의 문서를 생성합니다.
- $project: 출력 결과에서 불필요한 필드(예: _id)를 제거합니다.
- $out: 처리된 결과를 지정한 새 컬렉션에 저장합니다.
1. 샘플 컬렉션 생성
먼저 배열 필드를 포함한 문서들로 테스트 컬렉션을 만들어 보겠습니다.
> db.demo757.insertOne(
... {
... "id": 101,
... "Name": ["John", "Bob", "Chris"]
... }
... );
{
"acknowledged" : true,
"insertedId" : ObjectId("5eb025745637cd592b2a4ae2")
}
> db.demo757.insertOne(
... {
... "id": 102,
... "Name": ["David"]
... }
... );
{
"acknowledged" : true,
"insertedId" : ObjectId("5eb025755637cd592b2a4ae3")
}2. 저장된 문서 확인
find() 메서드를 사용하여 컬렉션의 모든 문서를 조회합니다.
> db.demo757.find();
위 쿼리는 다음과 같은 결과를 출력합니다.
{ "_id" : ObjectId("5eb025745637cd592b2a4ae2"), "id" : 101, "Name" : [ "John", "Bob", "Chris" ] }
{ "_id" : ObjectId("5eb025755637cd592b2a4ae3"), "id" : 102, "Name" : [ "David" ] }3. $unwind와 $out으로 배열 분해 후 새 컬렉션에 저장
다음은 배열이 포함된 하나의 레코드를 새 컬렉션의 여러 레코드로 변환하는 쿼리입니다.
> db.demo757.aggregate([
... {$unwind: '$Name'},
... {$project: {_id: 0}},
... {$out: 'demo758'}
... ])
> db.demo758.find();실행 결과는 다음과 같습니다.
{ "_id" : ObjectId("5eb02582192bedc4738b5881"), "id" : 101, "Name" : "John" }
{ "_id" : ObjectId("5eb02582192bedc4738b5882"), "id" : 101, "Name" : "Bob" }
{ "_id" : ObjectId("5eb02582192bedc4738b5883"), "id" : 101, "Name" : "Chris" }
{ "_id" : ObjectId("5eb02582192bedc4738b5884"), "id" : 102, "Name" : "David" }결과 분석
출력 결과를 보면 원래 문서(id: 101)에 배열로 저장되어 있던 "John", "Bob", "Chris" 세 개의 값이 각각 별도의 문서로 분리된 것을 확인할 수 있습니다. id: 102의 "David"처럼 배열에 요소가 하나뿐인 경우에는 그대로 하나의 문서로 유지됩니다.
여기서 $project: {_id: 0} 단계는 기존 _id 값을 제거하기 위해 사용됩니다. 이렇게 하면 $out이 새 컬렉션에 문서를 저장할 때 자동으로 새로운 ObjectId를 부여하므로, 원본 문서의 _id가 중복되어 충돌이 발생하는 것을 방지할 수 있습니다.
참고로 $out은 대상 컬렉션이 이미 존재하는 경우 해당 컬렉션의 내용을 완전히 대체하므로, 기존 데이터를 덮어쓰지 않으려면 $merge 연산자를 사용하는 것이 더 안전합니다.