프로그래밍/Database
💻 [MongoDB] 고급 파이프라인 연산자
프로그래민구찌
2025. 5. 2. 19:50
🎯 개요
MongoDB Aggregation은 단순히 그룹핑하고 계산하는 데 그치지 않고,
문서 구조를 바꾸거나 배열을 펼치거나, 특정 필드만 문서로 끌어올리는 등 다양한 데이터 전처리도 지원합니다.
이번 글에서는 $addFields, $unwind, $replaceRoot를 하나씩 실습해보며
복잡한 문서를 어떻게 원하는 형태로 바꿀 수 있는지 이해해보았습니다.
🧪 샘플 데이터 – surveys 컬렉션
db.surveys.insertMany([
{
respondent: "user1",
age: 29,
answers: [
{ question: "Q1", answer: "A" },
{ question: "Q2", answer: "B" }
]
},
{
respondent: "user2",
age: 34,
answers: [
{ question: "Q1", answer: "C" },
{ question: "Q2", answer: "A" }
]
}
])
🧮 고급 연산자 요약표
연산자 | 설명 | 예시 |
$addFields | 기존 문서에 필드추가 | { $addFields: { fullAge: "$age" } } |
$unwind | 배열 필드를 문서별로 펼침 | { $unwind: "$answers" } |
$replaceRoot | 전체 문서를 특정 필드로 교체 | { $replaceRoot: { newRoot: "$answers" } } |
1️⃣ $addFields – 새 필드 추가
✔ 10년 후 나이 필드 추가
db.surveys.aggregate([
{
$addFields: {
ageInTenYears: { $add: ["$age", 10] }
}
}
])
-- 결과
{ "_id" : ObjectId("6814212f9082a9a00776ebef"), "respondent" : "user1", "age" : 29, "answers" : [ { "question" : "Q1", "answer" : "A" }, { "question" : "Q2", "answer" : "B" } ], "ageInTenYears" : 39 }
{ "_id" : ObjectId("6814212f9082a9a00776ebf0"), "respondent" : "user2", "age" : 34, "answers" : [ { "question" : "Q1", "answer" : "C" }, { "question" : "Q2", "answer" : "A" } ], "ageInTenYears" : 44 }
처음엔 $project로도 비슷하게 할 수 있지 않나 싶었는데,
$addFields는 기존 필드 그대로 유지한 채 필요한 것만 추가할 수 있어서 더 간편했습니다.
2️⃣ $unwind – 배열 펼치기
✔ answers 배열을 하나씩 문서로 분해
db.surveys.aggregate([
{ $unwind: "$answers" }
])
-- 결과
{ "_id" : ObjectId("6814212f9082a9a00776ebef"), "respondent" : "user1", "age" : 29, "answers" : { "question" : "Q1", "answer" : "A" } }
{ "_id" : ObjectId("6814212f9082a9a00776ebef"), "respondent" : "user1", "age" : 29, "answers" : { "question" : "Q2", "answer" : "B" } }
{ "_id" : ObjectId("6814212f9082a9a00776ebf0"), "respondent" : "user2", "age" : 34, "answers" : { "question" : "Q1", "answer" : "C" } }
{ "_id" : ObjectId("6814212f9082a9a00776ebf0"), "respondent" : "user2", "age" : 34, "answers" : { "question" : "Q2", "answer" : "A" } }
aanswers 배열이 있으면 질문이 여러 개라서 분석이 어려웠는데,
$unwind를 쓰니까 배열 요소 하나하나가 독립 문서처럼 분리돼서 훨씬 다루기 쉬워졌습니다.
3️⃣ $replaceRoot – 문서 구조 재정의
✔ answers 항목을 최상위 문서로 설정
db.surveys.aggregate([
{ $unwind: "$answers" },
{ $replaceRoot: { newRoot: "$answers" } }
])
-- 결과
{ "question" : "Q1", "answer" : "A" }
{ "question" : "Q2", "answer" : "B" }
{ "question" : "Q1", "answer" : "C" }
{ "question" : "Q2", "answer" : "A" }
$unwind만 쓰면 문서 안에 answers가 남아있지만,
$replaceRoot까지 조합하면 아예 그 배열 요소 자체가 문서가 됩니다.
중첩 구조를 평탄화할 때 정말 유용하다고 느꼈습니다.
📌 실전 예제 조합
✔ 응답자별 Q1 답변만 추출
db.surveys.aggregate([
{ $unwind: "$answers" },
{ $match: { "answers.question": "Q1" } },
{
$project: {
_id: 0,
respondent: 1,
question: "$answers.question",
answer: "$answers.answer"
}
}
])
-- 결과
{ "respondent" : "user1", "question" : "Q1", "answer" : "A" }
{ "respondent" : "user2", "question" : "Q1", "answer" : "C" }
실제로는 조건 필터링과 가공을 함께 하게 되는데,
$unwind → $match → $project 조합이 가장 기본적인 데이터 추출 흐름처럼 느껴졌습니다.
🧠 회고 및 팁
- $addFields는 기존 필드를 유지하면서 계산 필드를 추가할 때 쓰면 훨씬 편합니다.
- $unwind는 배열 필터링과 분석에서 거의 필수 도구라고 느껴졌고,
- $replaceRoot는 중첩된 문서를 평탄화해서 딱 필요한 데이터만 남기기 좋았습니다.
- 세 연산자를 조합하면 복잡한 JSON 구조도 SQL 테이블처럼 편하게 다룰 수 있다는 확신이 생겼습니다.