배열 안의 하위 문서(subdocument) 중에서 특정 필드만 가진 항목만 골라내고 싶은 경우가 종종 있습니다. 이럴 때는 MongoDB의 aggregate() 메서드를 활용하면 됩니다. 이 글에서는 $match, $unwind, $project 파이프라인 단계를 조합하여 원하는 결과를 얻는 과정을 단계별로 살펴보겠습니다.
1. 샘플 컬렉션 생성하기
먼저 실습용 컬렉션을 만들어 보겠습니다. 아래 예제에서는 demo285라는 컬렉션에 details 배열 필드를 가진 문서 하나를 삽입합니다. 배열에는 Name 필드를 가진 요소와 Name2 필드를 가진 요소가 섞여 있습니다.
> db.demo285.insertOne(
... {
... details : [
... {
... Name : "Chris"
... },
... {
... Name2: "Bob"
... },
... {
... Name: "Mike"
... }
... ]
... }
... )
{
"acknowledged" : true,
"insertedId" : ObjectId("5e4abffef49383b52759cbb9")
}
2. 저장된 문서 확인하기
find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 조회할 수 있습니다.
> db.demo285.find();
위 명령을 실행하면 다음과 같은 결과가 출력됩니다.
{ "_id" : ObjectId("5e4abffef49383b52759cbb9"), "details" : [ { "Name" : "Chris" }, { "Name2" : "Bob" }, { "Name" : "Mike" } ] }
3. 프로젝션 필드 기준으로 하위 문서 제한하기
다음은 Name 필드를 가진 하위 문서만 남기고 나머지는 걸러내는 집계(aggregate) 쿼리입니다. 각 파이프라인 단계의 역할은 다음과 같습니다.
- $match:
details.Name필드가 존재하는 문서만 1차로 필터링합니다. - $unwind:
details배열을 개별 문서 단위로 분해합니다. - $match: 분해된 각 요소 중
Name필드가 있는 것만 다시 한번 필터링합니다. - $project:
Name값만 최종 출력하고_id는 제외합니다.
> db.demo285.aggregate(
... [
... { $match:
... {'details.Name' :
... { $exists: 1 }
... }
... },
... { $unwind: "$details" },
... { $match:
... {'details.Name' :
... { $exists: 1 }
... }
... },
... { $project: { Name: "$details.Name", _id: 0 } }
... ])
쿼리를 실행하면 Name2 필드만 가진 "Bob" 항목은 제외되고, Name 필드를 가진 항목만 다음과 같이 출력됩니다.
{ "Name" : "Chris" }
{ "Name" : "Mike" }
마무리
이처럼 aggregate() 파이프라인에서 $unwind와 $match를 조합하면 배열 내부의 하위 문서를 특정 필드 기준으로 손쉽게 필터링할 수 있습니다. 참고로 MongoDB 3.2 이상에서는 $filter 연산자를 사용하면 $unwind 없이도 배열 요소를 조건에 따라 걸러낼 수 있으므로, 데이터 구조와 용도에 맞게 적절한 방식을 선택하시기 바랍니다.