Computer >> 컴퓨터 >  >> 프로그래밍 >> MongoDB

MongoDB Aggregate 완벽 가이드: $unwind, $group, $project로 데이터 재구성하기

MongoDB Aggregate 파이프라인 핵심 연산자 이해하기

MongoDB의 애그리게이션(Aggregation) 프레임워크는 문서를 여러 단계(stage)로 처리하며 데이터를 변환·집계할 수 있는 강력한 도구입니다. 이 글에서는 자주 함께 사용되는 세 가지 연산자를 살펴보겠습니다.

  • $unwind : 입력 문서의 배열(array) 필드를 분해하여, 배열의 각 요소마다 하나의 문서를 출력합니다.
  • $group : 지정된 _id 표현식을 기준으로 문서들을 그룹화하고, 각 고유 그룹별로 하나의 결과 문서를 생성합니다.
  • $project : 다음 파이프라인 단계로 전달할 필드를 선택하거나 제외하여 원하는 형태의 문서를 만듭니다.

1단계: 샘플 컬렉션 생성 및 문서 삽입

먼저 demo238 컬렉션에 테스트용 문서를 삽입해 보겠습니다.

> db.demo238.insertOne(
...   {
...     "EmailId" : "John@gmail.com",
...     "details" : [
...        {
...           "Name" : "Bob",
...           "isActive" : true
...        }
...     ]
...   }
...);
{
    "acknowledged" : true,
    "insertedId" : ObjectId("5e4418e3f4cebbeaebec5152")
}
>
> db.demo238.insertOne(
...   {
...     "EmailId" : "Chris@gmail.com",
...     "details" : [
...        {
...           "Name" : "David"
...        }
...     ]
...   }
...);
{
    "acknowledged" : true,
    "insertedId" : ObjectId("5e4418e3f4cebbeaebec5153")
}
>
> db.demo238.insertOne(
...   {
...     "EmailId" : "Mike@gmail.com",
...     "details" : [
...        {
...           "Name" : "Carol",
...           "isActive" : true
...        }
...     ]
...   }
...);
{
    "acknowledged" : true,
    "insertedId" : ObjectId("5e4418e4f4cebbeaebec5154")
}

2단계: find() 메서드로 전체 문서 조회

find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 확인할 수 있습니다.

> db.demo238.find().pretty();

위 명령을 실행하면 다음과 같은 결과가 출력됩니다.

{
    "_id" : ObjectId("5e4418e3f4cebbeaebec5152"),
    "EmailId" : "John@gmail.com",
    "details" : [
       {
          "Name" : "Bob",
          "isActive" : true
       }
    ]
}
{
    "_id" : ObjectId("5e4418e3f4cebbeaebec5153"),
    "EmailId" : "Chris@gmail.com",
    "details" : [
       {
          "Name" : "David"
       }
    ]
}
{
    "_id" : ObjectId("5e4418e4f4cebbeaebec5154"),
    "EmailId" : "Mike@gmail.com",
    "details" : [
       {
          "Name" : "Carol",
          "isActive" : true
       }
    ]
}

3단계: $match, $unwind, $group을 조합한 애그리게이션 쿼리 실행

다음은 배열 필드를 분해(unwind)하고, 이름 기준으로 그룹화(group)한 뒤 필요한 필드만 추출하는 애그리게이션 쿼리입니다.

> db.demo238.aggregate(
...   [
...      { "$match": { "details.isActive": true } },
...      { "$unwind": "$details" },
...      { "$match": { "details.isActive": true } },
...      { "$group": {
...         "_id": "$details.Name",
...         "active": { "$first": "$_id" }
...      }}
...   ],
...   function(err,result) {
...
...   }
...);

쿼리 실행 결과는 다음과 같습니다.

{ "_id" : "Carol", "active" : ObjectId("5e4418e4f4cebbeaebec5154") }
{ "_id" : "Bob", "active" : ObjectId("5e4418e3f4cebbeaebec5152") }

쿼리 동작 방식 상세 설명

각 단계가 어떤 역할을 하는지 순서대로 살펴보겠습니다.

  1. 첫 번째 $match : details.isActivetrue인 문서만 미리 걸러냅니다. 초기 단계에서 불필요한 문서를 제거하면 후속 연산의 부담이 줄어들고, 인덱스를 활용할 수도 있습니다.
  2. $unwind : details 배열을 분해하여, 배열 요소 하나당 하나의 문서로 펼칩니다.
  3. 두 번째 $match : 배열이 분해된 이후 각 요소 수준에서 isActive 조건을 다시 검사합니다. 이렇게 하면 배열 내 일부 요소만 활성 상태인 경우에도 정확하게 필터링할 수 있습니다.
  4. $group : details.Name 값을 기준으로 문서를 그룹화하고, $first 연산자를 사용해 해당 그룹에 속한 첫 번째 문서의 원본 _id(ObjectId)를 active 필드로 저장합니다.

정리

이 예제에서 알 수 있듯이, $unwind → $match → $group 순서로 파이프라인을 구성하면 배열 데이터를 유연하게 평탄화(flatten)하고 조건에 맞는 데이터만 모아 원하는 형태로 가공할 수 있습니다. 실무에서는 여기에 $project를 추가해 최종 출력 필드를 더욱 세밀하게 제어하거나, $sort, $limit 등의 단계를 조합해 결과를 정렬·제한할 수도 있습니다.