PromleeBlog
sitemap
aboutMe

posting thumbnail
MongoDB 쿼리와 집계 - MongoDB 올인 6일차
MongoDB Queries and Aggregation - All-in Day 6

📅

🚀

들어가기 전에 🔗

지난 시간에는 MongoDB의 기본적인 데이터 조작 방법인 CRUD(Create, Read, Update, Delete)를 배웠습니다. 데이터를 넣고, 보고, 고치고, 지우는 기본기를 익혔죠.
오늘은 이 중에서 데이터를
읽는(Read)
작업을 훨씬 더 강력하고 유연하게 만들어주는 방법, 바로
쿼리(Query)
집계(Aggregation)
에 대해 자세히 알아볼 시간입니다.

🚀

기본 쿼리 확장하기 🔗

지난 시간에 find 명령어를 사용하여 데이터를 조회하는 기본적인 방법을 배웠습니다.
이번에는 다양한
쿼리 연산자
를 사용하여 더 복잡하고 세밀한 조건으로 데이터를 찾아내는 방법을 알아보겠습니다.
쿼리 연산자는 find 명령어의 첫 번째 인자인 조건 객체 안에서 필드 값과 함께 사용됩니다. 보통 달러 기호($)로 시작하는 이름을 가집니다.

비교 연산자: 값 비교하기 🔗

필드의 값을 특정 값과 비교하여 문서를 찾습니다.
// users 컬렉션에서 나이가 30세 초과인 사용자 찾기
db.users.find({ age: { $gt: 30 } })
 
// users 컬렉션에서 취미(hobbies)에 "게임" 또는 "영화 감상"이 포함된 사용자 찾기
db.users.find({ hobbies: { $in: ["게임", "영화 감상"] } })

논리 연산자: 조건 결합하기 🔗

여러 개의 조건을 논리적으로 결합하여 문서를 찾습니다.
// users 컬렉션에서 나이가 25세 미만이거나 상태(status)가 'inactive'인 사용자 찾기
db.users.find({ $or: [ { age: { $lt: 25 } }, { status: "inactive" } ] })
 
// AND 연산은 기본적으로 쉼표로 연결됩니다.
// 나이가 20세 이상이고(AND) 상태가 'active'인 사용자
db.users.find({ age: { $gte: 20 }, status: "active" })

요소 연산자: 필드의 존재 여부나 타입 확인하기 🔗

// users 컬렉션에서 'email' 필드가 존재하는 사용자 찾기
db.users.find({ email: { $exists: true } })
 
// users 컬렉션에서 'age' 필드의 타입이 숫자인 사용자 찾기 (BSON 타입 번호 사용)
// 숫자 타입(Double, 32-bit integer, 64-bit integer 등)을 나타내는 번호 또는 문자열 "number" 사용 가능
db.users.find({ age: { $type: "number" } })

기타 유용한 연산자 🔗

// users 컬렉션에서 이름(name)이 '김'으로 시작하는 사용자 찾기 (정규표현식 사용)
db.users.find({ name: { $regex: /^김/ } })

🚀

Aggregation Framework 🔗

단순히 조건에 맞는 데이터를 찾는 것을 넘어, 데이터를
변형하고, 그룹화하고, 계산하여
의미 있는 결과나 통계를 얻고 싶을 때 사용하는 것이 바로 MongoDB의
Aggregation Framework
(집계 프레임워크)입니다.
데이터는 여러 단계로 구성된
파이프라인(Pipeline)
을 통과하며 각
스테이지(Stage)
에서 정의된 작업에 따라 변환됩니다.
Aggregation 파이프라인
Aggregation 파이프라인
Aggregation Framework는 MongoDB Shell에서 aggregate 명령어를 사용하여 실행합니다.
이 명령어의 인자로는 스테이지 문서들이 담긴 배열을 전달합니다.
db.collection.aggregate([
  { <stage1> },
  { <stage2> },
  // ... 더 많은 스테이지
])

🚀

주요 Aggregation 스테이지 알아보기 🔗

Aggregation 파이프라인에서 자주 사용되는 핵심 스테이지들을 살펴보겠습니다.

$match 🔗

파이프라인으로 들어오는 문서들을
필터링
하는 스테이지입니다.
find 명령어의 조건 객체와 동일한 쿼리 연산자를 사용하여, 특정 조건을 만족하는 문서만 다음 스테이지로 전달합니다.
파이프라인 초반에 사용하여 처리할 문서 수를 줄이는 것이 성능에 유리합니다.
// 예: 상태(status)가 'active'인 사용자 문서만 다음 단계로 넘김
{ $match: { status: "active" } }

$project 🔗

문서의
구조를 변경
하는 스테이지입니다.
// 예: name 필드를 'userName'으로 바꾸고, age 필드만 포함하며, _id는 제외
{ $project: { _id: 0, userName: "$name", age: 1 } }
// 필드 값을 참조할 때는 '$필드명' 형식을 사용합니다.

$group 🔗

문서들을 특정
키(key)
를 기준으로
그룹화
하고, 각 그룹별로 집계 연산(합계, 평균, 개수 등)을 수행합니다.
그룹화의 기준이 되는 키는 _id 필드에 지정합니다.
// 예: 상태(status)별로 사용자 수를 계산
{
  $group: {
    _id: "$status", // status 필드 값으로 그룹화
    count: { $sum: 1 } // 각 그룹의 문서 개수를 세어서 count 필드에 저장
  }
}
// 결과 예시: { _id: "active", count: 15 }, { _id: "inactive", count: 5 }
주요 집계 연산자
: $sum, $avg, $min, $max, $push(그룹 내 필드 값을 배열로 모음), $first, $last 등.

$sort 🔗

문서들을 특정 필드 기준으로
정렬
합니다.
필드명에 1을 주면 오름차순, -1을 주면 내림차순으로 정렬합니다.
// 예: 나이(age)를 기준으로 내림차순 정렬
{ $sort: { age: -1 } }

$limit 🔗

파이프라인을 통과하는 문서의
개수를 제한
합니다.
// 예: 최대 5개의 문서만 다음 단계로 넘김
{ $limit: 5 }

$skip 🔗

지정된 개수만큼의 문서를
건너뛰고
다음 문서부터 처리합니다. 페이징(Pagination) 구현 시 $sort 와 함께 자주 사용됩니다.
// 예: 정렬된 결과 중 처음 10개 문서를 건너뜀
{ $skip: 10 }

$unwind 🔗

문서 내의
배열 필드
를 풀어서, 배열의 각 요소를 포함하는
개별 문서
로 만듭니다.
// 문서: { _id: 1, name: "A", hobbies: ["독서", "영화"] }
// $unwind 스테이지 후 결과:
// { _id: 1, name: "A", hobbies: "독서" }
// { _id: 1, name: "A", hobbies: "영화" }
{ $unwind: "$hobbies" } // hobbies 배열을 풀어냄

$lookup 🔗

현재 컬렉션의 문서를 다른 컬렉션의 문서와
결합(조인)
하는 스테이지입니다. 관계형 데이터베이스의 JOIN과 유사한 기능을 수행합니다. (참조 방식으로 모델링된 데이터를 합칠 때 유용)
// 예: orders 컬렉션의 productId 필드를 사용하여 products 컬렉션과 조인
{
  $lookup: {
    from: "products", // 조인할 대상 컬렉션 이름
    localField: "productId", // 현재(orders) 컬렉션의 조인 기준 필드
    foreignField: "_id", // 대상(products) 컬렉션의 조인 기준 필드
    as: "productInfo" // 조인된 결과(products 문서)를 저장할 새로운 필드 이름 (배열 형태)
  }
}

🚀

Aggregation 사용 예시 🔗

이제 위에서 배운 스테이지들을 조합하여 실제 집계 파이프라인을 만들어 봅시다.
예시 1: 상태(status)별 사용자 수를 계산하고, 사용자 수가 많은 순서로 정렬하기
db.users.aggregate([
  {
    $group: { // 1. status 필드로 그룹화하고 각 그룹의 개수 세기
      _id: "$status",
      userCount: { $sum: 1 }
    }
  },
  {
    $sort: { userCount: -1 } // 2. userCount 필드 기준으로 내림차순 정렬
  }
])
예시 2: 30세 이상인 사용자의 이름(name)과 이메일(email)만 추출하기 (단, 이메일 필드가 없는 경우는 제외)
db.users.aggregate([
  {
    $match: { // 1. 30세 이상이고 email 필드가 존재하는 문서만 필터링
      age: { $gte: 30 },
      email: { $exists: true }
    }
  },
  {
    $project: { // 2. name과 email 필드만 선택하고 _id는 제외
      _id: 0,
      name: 1,
      email: 1
    }
  }
])

🚀

면접 팁: 쿼리 및 집계 이해도 점검 🔗

쿼리와 집계는 MongoDB의 활용 능력을 보여주는 중요한 지표입니다. 면접에서 다음과 같은 질문이 나올 수 있습니다.
다양한 쿼리 연산자를 활용하는 능력과 Aggregation Framework의 동작 원리 및 주요 스테이지의 역할을 명확히 이해하고 설명할 수 있도록 준비하는 것이 중요합니다.

🚀

결론 🔗

오늘은 MongoDB에서 데이터를 효과적으로 조회하는 다양한
쿼리 연산자
와, 데이터를 변환하고 분석하는 강력한 도구인
Aggregation Framework
에 대해 깊이 있게 알아보았습니다.
특히 Aggregation 파이프라인을 구성하는 주요 스테이지들($match, $project, $group, $sort 등)의 역할과 사용법을 배우고 예제를 통해 실제로 어떻게 활용되는지 살펴보았습니다.
쿼리와 집계 기능을 잘 활용하면 MongoDB에 저장된 방대한 데이터 속에서 원하는 정보를 정확하게 찾아내고, 그 정보를 바탕으로 통찰력 있는 분석 결과를 도출할 수 있습니다.
이제 데이터를 효율적으로 다루는 방법을 익혔으니, 다음 시간에는 이러한 작업들이 더 빠르고 원활하게 이루어지도록 만드는 방법, 즉
MongoDB 성능 최적화
에 대해 알아보겠습니다. 인덱싱의 중요성과 쿼리 튜닝 방법에 대해 배울 예정이니 기대해 주세요!

참고 🔗