티스토리 뷰

DB/MongoDB

[MongoDB] 집계 프레임워크

snail voyager 2023. 5. 10. 10:40
728x90
반응형

7.1 파이프라인, 단계 및 조정 가능 항목

  • 집계 프레임워크는 파이프라인 개념을 기반
  • 단계마다 해당 입력에 다른 작업을 수행
  • 모든 단계의 입력과 출력은 도큐먼트(도큐먼트 스트림)
  • 집계 파이프라인의 개별 단계는 데이터 처리 단위
  • 한 번에 입력 도큐먼트 스트림을 하나씩 가져와서, 각 도큐먼트를 하나씩 처리하고, 출력 도큐먼트 스트림을 하나씩 생성

  • 각 단계는 knobs 또는 tunables 셋을 제공
    • 이 항목들을 조정해 각 단계를 매개변수로 지정함으로써 원하는 작업을 수행 가능
    • tuables은 필드를 수정, 산술 연산, 도큐먼트 재구성, 누산 작업 등 연산자 형태
  • 동일한 유형의 단계를 단일 파이프라인의 여러 번 포함 가능

7.2 단계 시작하기

  • aggregate는 집계 쿼리를 실행할 때 호출하는 메서드
  • 일치 단계는 컬렉션에 대해 필터링, 결과 도큐먼트를 한번에 하나씩 선출 단계로 전달
  • 선출 단계는 작업을 수행하고 도큐먼트 모양을 변경한 후 출력을 파이프라인에서 다시 전달
db.students.aggregate({$match : {class_id : 8919}});	//일치

db.students.aggregate([
    {$match : {class_id : 8919}}, 
    {$project : {		//선출
        _id:0,
        student_id: 1
    }}
]);
  • 선출 단계를 먼저 실행하고 제한을 실행해도 동일한 결과
  • 효율성을 위해 다른 단계로 전달해야 하는 도큐먼트 수 제한을 먼저 수행
  • 순서가 중요하다면 제한 단계 전에 정렬 수행
db.students.aggregate([
    {$match : {class_id : 8919}}, 
    {$limit : 5},		//제한
    {$project : {
        _id:0,
        student_id: 1
    }}
]);

db.students.aggregate([
    {$match : {class_id : 8919}}, 
    {$project : {
        _id:0,
        student_id: 1
    }},
    {$limit : 5}		//동일한 결과
]);

db.students.aggregate([
    {$match : {class_id : 8919}}, 
    {$sort : {student_id : 1}},		//제한 전에 정렬
    {$limit : 5},
    {$project : {
        _id:0,
        student_id: 1
    }}
]);

7.4 $project 연산자

  • 필드 선택: 특정 필드만을 출력에 포함하고자 할 때 사용
  • 필드 이름 변경: 출력에 포함되는 필드의 이름을 변경하고자 할 때 사용
  • 계산된 필드 추가: 새로운 필드를 추가하고, 해당 필드에 대한 계산을 수행하고자 할 때 사용
  • $add, $subtract, $multiply, $divide 등의 연산자를 사용하여 필드의 값을 계산
{ $project: { name: 1, age: 1 } }	//"name"과 "age" 필드만을 출력으로 선택

{ $project: { _id: 0, productName: "$name" } }	//"_id" 필드를 제외하고 "name" 필드를 "productName"으로 이름을 변경하여 출력

{ $project: { totalPrice: { $multiply: ["$price", "$quantity"] } } }	//"price"와 "quantity" 필드를 곱한 값을 "totalPrice" 필드로 추가

중첩 필드 승격

  • 중첩된 필드를 승격하면 중첩 구조를 제거하고 평면적인 필드 구조로 변환
  • $문자는 선출 단계에서 값을 지정하는데 사용, 해당 값이 필드 경로로 해석되고 각 필드에서 선출할 값을 선택하는데 사용됨을 나타냄
{
  _id: 1,
  info: {
    name: "John",
    age: 30
  }
}

db.collection.aggregate([
  {
    $project: {
      _id: 1,
      name: "$info.name",	//"info" 필드를 승격하여 "name"과 "age" 필드로 변환
      age: "$info.age"
    }
  }
])

{
  _id: 1,
  name: "John",
  age: 30
}

7.5 $unwind 연산자

  • 배열 필드를 각 원소로 분리하여 여러 개의 문서로 변환하는 집계 연산자
  • 배열 필드를 활용하여 집계 작업을 수행하는 데에 유용
{
  $unwind: {
    path: "<arrayField>",	//분리할 배열 필드의 이름
    includeArrayIndex: "<indexField>",	//배열의 인덱스를 저장할 필드의 이름을 지정
    preserveNullAndEmptyArrays: <boolean>	// true인 경우 비어 있는 배열이나 null 값이 포함된 문서도 결과에 포함
  }
}

{
  _id: 1,
  customer: "John",
  products: ["Apple", "Banana", "Orange"]
},
{
  _id: 2,
  customer: "Jane",
  products: ["Grapes", "Mango"]
}

db.orders.aggregate([
  { $unwind: "$products" }	//"products" 필드를 분리하여 각 원소를 개별 문서로 만든다
])

{
  _id: 1,
  customer: "John",
  products: "Apple"
},
{
  _id: 1,
  customer: "John",
  products: "Banana"
},
{
  _id: 1,
  customer: "John",
  products: "Orange"
},
{
  _id: 2,
  customer: "Jane",
  products: "Grapes"
},
{
  _id: 2,
  customer: "Jane",
  products: "Mango"
}
  • $unwind를 먼저 적용한 후에 $match를 적용하는 것이 일반적
{
  _id: 1,
  fruits: ["apple", "banana", "orange"]
}

db.collection.aggregate([
  { $match: { fruits: "apple" } },	//$match를 먼저 적용하고 $unwind를 적용
  { $unwind: "$fruits" }
]);

{
  _id: 1,
  fruits: ["apple", "banana", "orange"]	//"fruits" 필드의 값이 "apple"인 문서는 선택되지만, $unwind 이전의 형태를 유지
}

db.collection.aggregate([
  { $unwind: "$fruits" },		//$unwind를 적용하고, 그 다음에 $match를 적용
  { $match: { fruits: "apple" } }
])

{
  _id: 1,
  fruits: "apple"		//"fruits" 필드의 값이 "apple"인 문서만을 선택
}

7.6 배열 표현식

  • 선출 단계에서 배열 표현식을 사용하여 데이터를 가공하고 변환
  • 배열 표현식은 집계 파이프라인의 초기 단계에서 사용되며, 주로 $project 연산자와 함께 사용
  • 일반적으로 배열 표현식은 $map, $filter, $reduce와 같은 연산자와 함께 사용되어 배열 필드의 원소를 조작하고 변환하는 데 사용
{
  _id: 1,
  customer: "John",
  prices: [10, 15, 20]
},
{
  _id: 2,
  customer: "Jane",
  prices: [5, 12, 8]
}

db.orders.aggregate([
  {
    $project: {
      _id: 1,
      customer: 1,
      multipliedPrices: { 
      $map: { 
      	input: "$prices", 
      	as: "price", 
      	in: { $multiply: ["$$price", 10] } } }	//"prices" 필드의 각 원소에 10을 곱한 값을 "multipliedPrices" 필드로 추가
    }
  }
])

{
  _id: 1,
  customer: "John",
  multipliedPrices: [100, 150, 200]
},
{
  _id: 2,
  customer: "Jane",
  multipliedPrices: [50, 120, 80]
}
  • $$ 는 작업 중인 표현식 내에서 정의된 변수를 참조하는데 사용 ("as"에 정의된 변수를 참조하는 데 사용)
  • input 은 배열을 지정
  • as 는 배열에 사용할 이름을 지정
  • cond 는 조건을 지정
{
  _id: 1,
  customer: "John",
  products: [
    { name: "Apple", price: 10 },
    { name: "Banana", price: 15 },
    { name: "Orange", price: 20 }
  ]
},
{
  _id: 2,
  customer: "Jane",
  products: [
    { name: "Grapes", price: 5 },
    { name: "Mango", price: 12 },
    { name: "Kiwi", price: 8 }
  ]
}

db.orders.aggregate([
  {
    $project: {
      _id: 1,
      customer: 1,
      filteredProducts: {
        $filter: {
          input: "$products",
          as: "product",
          cond: { $gte: ["$$product.price", 10] }	//"products" 필드에서 가격이 10 이상인 상품들만을 선택
        }
      }
    }
  }
])

{
  _id: 1,
  customer: "John",
  filteredProducts: [
    { name: "Apple", price: 10 },
    { name: "Banana", price: 15 },
    { name: "Orange", price: 20 }
  ]
},
{
  _id: 2,
  customer: "Jane",
  filteredProducts: [
    { name: "Mango", price: 12 }
  ]
}
  • $arrayElemAt 연산자를 사용하면 배열 내 특정 슬롯에서 요소를 선택 가능
  • 인덱스는 0부터 시작하며, 음수 인덱스는 배열의 끝에서부터 역순으로 원소를 선택 (마지막 요소는 -1)
{
  _id: 1,
  customer: "John",
  products: ["Apple", "Banana", "Orange"]
}

db.orders.aggregate([
  {
    $project: {
      _id: 1,
      customer: 1,
      secondProduct: { $arrayElemAt: ["$products", 1] }
    }
  }
])

{
  _id: 1,
  customer: "John",
  secondProduct: "Banana"
}
  • $slice 표현식은 배열 필드의 원하는 범위나 개수의 원소를 선택하여 반환하는 데 활용
{
  _id: 1,
  customer: "John",
  products: ["Apple", "Banana", "Orange", "Grapes", "Mango"]
}

db.orders.aggregate([
  {
    $project: {
      _id: 1,
      customer: 1,
      selectedProducts: { $slice: ["$products", 1, 3] }
    }
  }
])

{
  _id: 1,
  customer: "John",
  selectedProducts: ["Banana", "Orange", "Grapes"]
}
  • $size 표현식은 배열 요소 개수 값을 제공
  • $project 단계에서 배열 필드 $max 사용
  • $project 단계에서 $reduce와 $cond 연산자를 사용하여 "products" 배열에서 최댓값을 선택합니다. $reduce 연산자는 input으로 배열을 받고, initialValue로 첫 번째 원소를 설정합니다. in 절에서는 $cond 연산자를 사용하여 현재 원소 $$this와 누적된 최댓값 $$value를 비교하여 더 큰 값을 선택
{
  _id: 1,
  customer: "John",
  products: [5, 10, 8, 12, 7]
},
{
  _id: 2,
  customer: "Jane",
  products: [15, 9, 11, 6, 14]
},
{
  _id: 3,
  customer: "Alice",
  products: [3, 7, 13, 9, 4]
}

db.orders.aggregate([
  {
    $project: {
      _id: 1,
      customer: 1,
      maxProduct: {
        $reduce: {
          input: "$products",
          initialValue: { $arrayElemAt: ["$products", 0] },
          in: {
            $cond: {
              if: { $gt: ["$$this", "$$value"] },
              then: "$$this",
              else: "$$value"
            }
          }
        }
      }
    }
  }
])

{
  _id: 1,
  customer: "John",
  maxProduct: 12
},
{
  _id: 2,
  customer: "Jane",
  maxProduct: 15
},
{
  _id: 3,
  customer: "Alice",
  maxProduct: 13
}

7.7 누산기

  • 집계 프레임워크가 제공하는 누산기를 사용하면 특정 필드의 모든 값 합산, 평균 계산 등 작업 가능
  • 선출 단계에서는 $sum, $avg와 같은 누산기가 단일 도큐먼트 내 배열에서만 작동
  • 그룹단계에서는 누산기가 여러 도큐먼트 값에 걸쳐 계산 수행
db.companies.aggregate([
	{$match: {"funding_rounds" : {$exists: true, $ne: []}},
    {$project: {
    	_id : 0,
        name : 1,
        largest_round : {$max: "$funding_rounds.raised_amount"}
    }}
])

7.8 그룹단계

  • $group 연산자를 사용하여 데이터를 그룹화하는 단계
  • $group 연산자는 특정 필드를 기준으로 그룹을 생성하고, 그룹 내에서 필요한 계산이나 집계 작업을 수행
{
  $group: {
    _id: <expression>,  // 그룹화할 기준 필드 또는 표현식
    field1: { <accumulator1> : <expression1> },  // 필요한 누산기와 표현식
    field2: { <accumulator2> : <expression2> },
    ...
  }
}
  • $sum: 필드 값을 합산합니다.
  • $avg: 필드 값을 평균 계산합니다.
  • $min: 필드의 최솟값을 선택합니다.
  • $max: 필드의 최댓값을 선택합니다.
  • $first: 그룹 내에서 첫 번째 문서의 값을 선택합니다.
  • $last: 그룹 내에서 마지막 문서의 값을 선택합니다.
  • $push: 그룹 내의 값들을 배열로 수집합니다.
  • $addToSet: 그룹 내의 고유한 값들을 배열로 수집합니다.
db.orders.aggregate([
  {
    $group: {
      _id: "$customer",		//"orders" 컬렉션을 "customer" 필드를 기준으로 그룹화
      totalAmount: { $sum: "$amount" }	//그룹 내에서 "amount" 필드 값을 합산하여 "totalAmount" 필드를 계산
    }
  }
])

7.8.1 그룹 단계의 _id 필드

그룹 값에 레이블을 지정하지 않으면 그룹화한다는 점이 분명하지 않기 때문에 그룹화할 값에 명시적으로 레이블 지정

db.companies.aggregate([
	{$match: {founded_year: {$gte: 2010}},
    	{$group: {
    		_id: {founded_year: "$founded_year", category_code: "$category_code"},	//레이블 지정
        	companies: {$push : "$name"}
    	}},
    	{$sort: {"_id.founded_year" : 1}}
]).pretty()

7.8.2 그룹 vs. 선출

  • $push, $first, $last 표현식은 그룹 단계에서만 작동
  • 그룹 단계가 도큐먼트의 입력 스트림을 가져와 각 도큐먼트를 처리해 값을 축적하도록 설계
  • 선출 단계는 도큐먼트를 개별적으로 재구성하도록 설계

7.9 집계 파이프라인 결과를 컬렉션에 쓰기

  • 집계 파이프라인에서 생성된 도큐먼트를 컬렉션에 쓸 수 있는 두가지 단계
  • 집계 파이프라인 마지막 단계에서 수행
  • $out
    • 동일한 데이터베이스에만 쓸 수 있음
    • 기존 컬렉션이 있으면 덮어씀
    • 샤딩된 컬렉션 쓸 수 없음
  • $merge
    • 샤딩 여부에 관계없이 모든 데이터베이스와 컬렉션에 쓸 수 있음
    • 기존 컬렉션으로 작업할 때 결과를 통합할 수 있음
    • 출력 컬렉션의 내용이 점진적으로 갱신되는 주문식의 구체화된 뷰를 생성
728x90
반응형

'DB > MongoDB' 카테고리의 다른 글

[MongoDB] 트랜잭션  (0) 2023.07.02
[MongoDB] 애플리케이션 설계  (0) 2023.06.20
[MongoDB] 공간 정보 인덱스  (0) 2023.04.26
[MongoDB] 인덱싱2  (0) 2023.04.18
[MongoDB] 인덱싱  (0) 2023.03.22
반응형
300x250