MongoDB에서 배열 형태로 저장된 데이터 안에서 고유값(distinct)을 선택하고 각 값이 등장한 횟수까지 함께 집계해야 하는 경우가 자주 있습니다. 이 글에서는 애그리게이션(aggregate) 파이프라인을 활용해 이 작업을 수행하는 방법을 단계별로 살펴보겠습니다.
1. 샘플 컬렉션 생성하기
먼저 문서를 담고 있는 컬렉션을 생성합니다. 아래 예제에서는 demo586이라는 컬렉션에 학생 이름과 점수를 담은 details 배열 필드를 가진 문서 두 개를 삽입합니다.
> db.demo586.insertOne(
... {"details": [
... {
... "Name":"Chris",
... "Marks":71
... },
... {
... "Name":"Chris",
... "Marks":61
... },
... {
... "Name":"David",
... "Marks":81
... }
... ]
... }
... );
{
"acknowledged" : true,
"insertedId" : ObjectId("5e9200fefd2d90c177b5bcc7")
}
> db.demo586.insertOne(
... {"details": [
... {
... "Name":"Chris",
... "Marks":71
... },
... {
... "Name":"Carol",
... "Marks":61
... },
... {
... "Name":"David",
... "Marks":81
... }
... ]
... }
... );
{
"acknowledged" : true,
"insertedId" : ObjectId("5e9200fefd2d90c177b5bcc8")
}2. find()로 전체 문서 확인하기
find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 확인할 수 있습니다.
> db.demo586.find();
위 명령을 실행하면 다음과 같은 결과가 출력됩니다.
{ "_id" : ObjectId("5e9200fefd2d90c177b5bcc7"), "details" : [
{ "Name" : "Chris", "Marks" : 71 },
{ "Name" : "Chris", "Marks" : 61 },
{ "Name" : "David", "Marks" : 81 }
] }
{ "_id" : ObjectId("5e9200fefd2d90c177b5bcc8"), "details" : [
{ "Name" : "Chris", "Marks" : 71 },
{ "Name" : "Carol", "Marks" : 61 },
{ "Name" : "David", "Marks" : 81 }
] }3. 고유값 선택 및 개수 집계 쿼리
이제 핵심인 애그리게이션 쿼리입니다. 다음 파이프라인은 각 이름(Name)별로 중복을 제거한 점수(Marks) 목록과 해당 점수가 나타난 횟수를 함께 반환합니다.
> var q= [
... { "$unwind": "$details" },
... {
... "$group": {
... "_id": {
... "Name": "$details.Name",
... "Marks": "$details.Marks"
... },
... "count": { "$sum": 1 }
... }
... },
... {
... "$group": {
... "_id": "$_id.Name",
... "distinctV": {
... "$addToSet": {
... "value": "$_id.Marks",
... "numberOfValues": "$count"
... }
... }
... }
... },
... {
... "$project": {
... "_id": 0,
... "Name": "$_id",
... "distinctV": 1
... }
... }
... ];
> db.demo586.aggregate(q);쿼리 실행 결과는 다음과 같습니다.
{ "distinctV" : [ { "value" : 61, "numberOfValues" : 1 } ], "Name" : "Carol" }
{ "distinctV" : [ { "value" : 71, "numberOfValues" : 2 }, { "value" : 61, "numberOfValues" : 1 } ], "Name" : "Chris" }
{ "distinctV" : [ { "value" : 81, "numberOfValues" : 2 } ], "Name" : "David" }파이프라인 단계별 동작 원리
- $unwind:
details배열을 개별 문서로 펼쳐서 각 요소를 독립적으로 처리할 수 있게 만듭니다. - $group (첫 번째): 이름과 점수 조합을 기준으로 그룹화하고,
$sum: 1로 각 조합이 등장한 횟수를 계산합니다. - $group (두 번째): 이름 기준으로 다시 그룹화하며,
$addToSet을 사용해 중복 없는 점수 값과 개수 정보를 배열로 모읍니다. - $project: 불필요한
_id필드를 제거하고 읽기 좋은 최종 출력 형태를 만듭니다.
결과 해석
- Carol: 점수 61점이 1번 등장했습니다.
- Chris: 점수 71점이 2번, 61점이 1번 등장했습니다.
- David: 점수 81점이 2번 등장했습니다.
이처럼 $unwind, $group, $addToSet을 조합하면 배열 필드 안의 데이터도 손쉽게 고유값 추출과 빈도 집계를 동시에 처리할 수 있습니다. 대량의 데이터를 다룰 때는 인덱스 설계와 함께 이 패턴을 활용하면 효율적인 분석 쿼리를 작성할 수 있습니다.