개요
MongoDB 컬렉션에서 각 그룹별로 특정 속성의 최댓값을 가진 문서를 조회해야 하는 경우가 종종 있습니다. 이럴 때는 집계 파이프라인(aggregation pipeline)에서 $sort 연산자와 $group 스테이지를 조합하면 간단하게 해결할 수 있습니다.
예제 데이터 준비하기
개념을 더 잘 이해하기 위해 먼저 문서가 포함된 컬렉션을 생성해 보겠습니다. 아래 쿼리는 maxAttributePerGroup 컬렉션에 학생 정보를 담은 문서들을 차례로 삽입합니다.
> db.maxAttributePerGroup.insertOne({"StudentFirstName":"John","StudentLastName":"Smith","StudentAge":29,"StudentId":10});
{
"acknowledged" : true,
"insertedId" : ObjectId("5c76ee341e9c5dd6f1f78277")
}
> db.maxAttributePerGroup.insertOne({"StudentFirstName":"Carol","StudentLastName":"Taylor","StudentAge":19,"StudentId":10});
{
"acknowledged" : true,
"insertedId" : ObjectId("5c76ee4e1e9c5dd6f1f78278")
}
> db.maxAttributePerGroup.insertOne({"StudentFirstName":"Adam","StudentLastName":"Smith","StudentAge":34,"StudentId":20});
{
"acknowledged" : true,
"insertedId" : ObjectId("5c76ee631e9c5dd6f1f78279")
}
> db.maxAttributePerGroup.insertOne({"StudentFirstName":"Bob","StudentLastName":"Taylor","StudentAge":58,"StudentId":20});
{
"acknowledged" : true,
"insertedId" : ObjectId("5c76ee791e9c5dd6f1f7827a")
}저장된 전체 문서 확인하기
find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 확인할 수 있습니다.
> db.maxAttributePerGroup.find().pretty();
출력 결과
{
"_id" : ObjectId("5c76ee341e9c5dd6f1f78277"),
"StudentFirstName" : "John",
"StudentLastName" : "Smith",
"StudentAge" : 29,
"StudentId" : 10
}
{
"_id" : ObjectId("5c76ee4e1e9c5dd6f1f78278"),
"StudentFirstName" : "Carol",
"StudentLastName" : "Taylor",
"StudentAge" : 19,
"StudentId" : 10
}
{
"_id" : ObjectId("5c76ee631e9c5dd6f1f78279"),
"StudentFirstName" : "Adam",
"StudentLastName" : "Smith",
"StudentAge" : 34,
"StudentId" : 20
}
{
"_id" : ObjectId("5c76ee791e9c5dd6f1f7827a"),
"StudentFirstName" : "Bob",
"StudentLastName" : "Taylor",
"StudentAge" : 58,
"StudentId" : 20
}현재 StudentId가 10인 그룹에는 John(29세)과 Carol(19세)이, StudentId가 20인 그룹에는 Adam(34세)과 Bob(58세)이 속해 있습니다. 우리의 목표는 각 그룹에서 나이(StudentAge)가 가장 많은 학생의 문서만 추출하는 것입니다.
그룹별 최대 속성 문서 조회하기
아래는 컬렉션에서 그룹별 최대 속성 값을 가진 문서를 가져오는 집계 쿼리입니다.
> db.maxAttributePerGroup.aggregate([
{"$sort":{"StudentId":1,"StudentAge":-1}},
{$group:{
"_id":"$StudentId",
"StudentAge":{"$first":"$StudentAge"},
"StudentFirstName":{"$first":"$StudentFirstName"},
"StudentLastName":{"$first":"$StudentLastName"}
}} ]).pretty();
출력 결과
{
"_id" : 20,
"StudentAge" : 58,
"StudentFirstName" : "Bob",
"StudentLastName" : "Taylor"
}
{
"_id" : 10,
"StudentAge" : 29,
"StudentFirstName" : "John",
"StudentLastName" : "Smith"
}동작 원리 정리
위 쿼리가 의도한 대로 작동하는 이유는 다음과 같습니다.
1. $sort 단계: {"StudentId": 1, "StudentAge": -1} 조건으로 정렬합니다. 즉, StudentId를 오름차순으로 정렬한 상태에서 같은 ID 그룹 안에서는 나이가 많은 순서(내림차순)로 배치됩니다.
2. $group 단계: _id를 $StudentId로 지정하여 동일한 학생 ID를 가진 문서들을 하나의 그룹으로 묶습니다.
3. $first 연산자: 정렬된 순서에서 각 그룹의 첫 번째 문서, 즉 나이가 가장 많은 학생의 이름과 나이 값을 선택합니다.
그 결과 StudentId 10 그룹에서는 John Smith(29세), StudentId 20 그룹에서는 Bob Taylor(58세)가 반환되는 것을 확인할 수 있습니다. 이 패턴은 '카테고리별 최고 매출 제품 조회', '부서별 최고 연봉자 조회' 등 다양한 실무 시나리오에 응용할 수 있습니다.