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") }쿼리 동작 방식 상세 설명
각 단계가 어떤 역할을 하는지 순서대로 살펴보겠습니다.
- 첫 번째 $match :
details.isActive가true인 문서만 미리 걸러냅니다. 초기 단계에서 불필요한 문서를 제거하면 후속 연산의 부담이 줄어들고, 인덱스를 활용할 수도 있습니다. - $unwind :
details배열을 분해하여, 배열 요소 하나당 하나의 문서로 펼칩니다. - 두 번째 $match : 배열이 분해된 이후 각 요소 수준에서
isActive조건을 다시 검사합니다. 이렇게 하면 배열 내 일부 요소만 활성 상태인 경우에도 정확하게 필터링할 수 있습니다. - $group :
details.Name값을 기준으로 문서를 그룹화하고,$first연산자를 사용해 해당 그룹에 속한 첫 번째 문서의 원본_id(ObjectId)를active필드로 저장합니다.
정리
이 예제에서 알 수 있듯이, $unwind → $match → $group 순서로 파이프라인을 구성하면 배열 데이터를 유연하게 평탄화(flatten)하고 조건에 맞는 데이터만 모아 원하는 형태로 가공할 수 있습니다. 실무에서는 여기에 $project를 추가해 최종 출력 필드를 더욱 세밀하게 제어하거나, $sort, $limit 등의 단계를 조합해 결과를 정렬·제한할 수도 있습니다.