(몽고DB 완벽 가이드) Chapter 7. 집계 프레임워크

이재문·2023년 11월 27일
  • mongoDB는 집계 프레임워크를 사용해 분석을 할 수 있는 도구를 지원한다.
    • 집계 프레임워크
    • 집계 단계
    • 집계 표현식
    • 집계 누산기

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

집계 파이프라인

  • 하나 이상의 컬렉션에 있는 도큐먼트에 대한 분석 수행
  • 파이프라인 개념을 기반으로 한다.
  • 집계 파이프라인들 통해 몽고 db 컬렉션에서 입력 → 컬렉션에서 나온 도큐먼트를 하나 이상의 단계를 거쳐 전달.
  • 단계마다 해당 입력에 다른 작업을 수행 모든 단계의 입출력은 도큐먼트.
    • Collection → 단계 → 단계 → 단계 → output
      • ___ : pipeline
  • 배시(bash)와 같은 리눅스 셸 파이프라인과 매우 유사한 개념이며, 단계마다 특정 작업을 수행
  • 각 단계는 특정한 형태의 도큐먼트 입력받고 특정 출력(도큐먼트 스트림)을 생성
  • 파이프라인 끝에는 find 쿼리와 같은 방식으로 출력에 접근

단계

  • 집계 파이프라인의 개별 단계는 데이터 처리 단위

  • 한번에 입력 도큐먼트 스트림을 하나씩 가져 온 다음, 각 도큐먼트를 하나씩 처리하고, 출력 도큐먼트 스트림을 하나씩 생성한다.

  • 각 단계에는 knobs 또는 tunables 셋을 제공한다.

  • 이 항목들을 조정해 각 단계를 매개변수로 지정함으로써 원하는 작업을 수행할 수 있다.

  • 사용 중인 특정 컬렉션과 도큐먼트에 수행할 작업에 따라 단계를 매개변수화한다.

  • tunables는 필드를 수정, 산술 연산, 도큐먼트 재구성, 누산 작업 등 연산자의 형태를 취함

  • 파이프라인은 mongoDB 컬렉션과 함께 작동.

  • 파이프라인은 단계로 구성되며 각 단계는 입력에 서로 다른 데이터 처리 작업을 수행하고, 출력으로 도큐먼트를 생성해 다음 단계로 전달한다.

7.2 단계 시작하기: 익숙한 작업들

> db.companies.aggregate([{$match: {founded_year: 2004}}])

> db.companies.find({founded_year: 2004})

// 같은 결과
  • _id필드 제외, name, founded_year 필드는 포함

> db.companies.aggregate([
	{$match: {founded_year: 2004}},
	{$project: { 
			_id: 0,
			name: 1,
			founded_year: 1
		}}
])

>> {"name": "이름1", "founded_year": 2004}
	 {"name": "이름2", "founded_year": 2004}
	 {"name": "이름3", "founded_year": 2004}
	 {"name": "이름4", "founded_year": 2004}
	 {"name": "이름5", "founded_year": 2004}
	 {"name": "이름6", "founded_year": 2004}
	 {"name": "이름7", "founded_year": 2004}
	 {"name": "이름8", "founded_year": 2004}
	 {"name": "이름9", "founded_year": 2004}
  • aggregate - 집계 쿼리 실행 메소드

  • 각 도큐먼트는 특정 단계 연산자를 규정해야 한다.

  • 위 쿼리는 2개의 단계를 가지고 있다.

    • 필터링 일치 단계
    • 출력 도큐먼트를 두 개의 필드로 제한 단계
  • 위의 쿼리와 동일하게 수행하고, 결과set은 5개로 제한하는 쿼리

    > db.companies.aggregate([
    	{$match: {founded_year: 2004}},
    	{$limit: 5},
    	{$project: { 
    			_id: 0,
    			name: 1,
    		}}
    ])
    
    >> {"name": "이름"}
    	 {"name": "이름1"}
    	 {"name": "이름2"}
    	 {"name": "이름3"}
    	 {"name": "이름4"}
    
    > db.companies.aggregate([
    	{$match: {founded_year: 2004}},
    	{$project: { 
    			_id: 0,
    			name: 1,
    		}},
    	{$limit: 5}
    ])
    
    • 제한을 선출 단계 이전에 수행했을 경우, 결과를 5개로 limit하기 이전에 선출 단계를 통해 선출 조건에 맞는 도큐먼트 모두를 전달해야 함

    • 파이프라인을 구축할 때 한 단계에서 다른 단계로 전달해야 하는 도큐먼트 수를 반드시 제한하자

      > db.companies.aggregate([
      	{$match: {founded_year: 2004} },
      	{$sort: {name: 1} },
      	{$limit: 5}
      	{$project: { 
      			_id: 0,
      			name: 1,
      		}},
      ]) 
    • 5개의 도큐먼트를 name의 알파벳 순으로 선출한다.

> db.companies.aggregate([
	{$match: {founded_year: 2004} },
	{$sort: {name: 1} },
	{$skip: 10},
	{$limit: 5},
	{$project: { 
			_id: 0,
			name: 1,
		}},
]) 
  • 정렬 이후 처음 10개의 도큐먼트를 skip 한 후 5개의 셋만을 제한
  1. founded_year가 2004인 도큐먼트 찾기
  2. name 을 기준으로 오름차순 정렬
  3. $skip 의 개수만큼 건너 뜀
  4. 최종 결과 $limit 만큼 제한
  5. name 만 출력되도록 도큐먼트 모양 변경

7.3 표현식

불리언 표현식

  • 불리언(boolean) 표현식을 사용하면 AND, OR, NOT 표현식을 쓸 수 있다.

집합 표현식

  • 집합 표현식을 사용하면 배열을 집합으로 사용 가능
  • 2개 이상의 집합의 교집합이나 합집합을 얻을 수 있다.

비교 표현식

  • 비교 표현식을 통해 범위 필터를 표현 가능

산술 표현식

  • ceiling, floor, 자연 로그, 로그를 계산할 수 있다.
  • *, / , +, - 간단한 연산 가능, 제곱근 도 가능

문자열 표현식

  • 연결, 하위 문자열 검색, 대소문자 및 텍스트 검색과 관련된 작업 가능

배열 표현식

  • 배열 요소 필터링, 배열 분할, 특정 배열에서 값 가져오기 등 배열 조작에 유용

가변적 표현식

  • 리터럴(literal), 날짜 값 구문 분석을 위한 식, 조건식을 사용한다.

누산기

  • 누산기는 합계, 기술 통계 및 기타 유형의 값을 계산하는 기능을 제공

7.4 $project

  • nested field 승격? promoting
    > db.companies.aggregate([
    		{$match: {"funding_rouds.investments.financial_org.permalink": "greylock"}},
    		{$project: {
    			_id: 0,
    			name: 1,
    			ipo: "$ipo.pub_year",
    			valuation: "$ipo.valuation_amount",
    			funders: "$funding_rouds.investments.financial_org.permalink"
    	}}
    ]).pretty()

7.5 $unwind

  • 배열 필드를 작업할 때 하나 이상의 unwind(전개) 단계를 포함해야한다.
{key1: "value1", key2: "value2", key3: ["elem1", "elem2", "elem3"]}

> $unwind

> {key1: "value1", key2: "value2", key3: "elem1"}
> {key1: "value1", key2: "value2", key3: "elem2"}
> {key1: "value1", key2: "value2", key3: "elem3"}

// $unwind는 입력 도큐먼트에서 배열을 가져오고, 해당 배열의 각 요소에 대한 출력 도큐먼트 생성
  • $unwind를 사용하면 배열 내 요소의 개수 만큼 도큐먼트 생성된다.

7.6 배열 표현식

필터 표현식

  • 필터 기준에 따라 배열에서 요소의 부분집합을 선택한다.
> db.companies.aggregate([
		{$match: {"funding_rouds.investments.financial_org.permalink": "greylock"}},
		{$project: {
			_id: 0,
			name: 1,
			founded_year: 1,
			rounds: {$filter: {
				input: "$funding_rounds",
				as: "rounds",
				cond: {$gte: ["$$round.raised_amount", 10000000]}}}
			}},
		{$match: {"rounds.investments.financial_org.permalink": "greylock"}},
]).pretty()
  • rounds 필드는 필터 표현식을 사용한다.
  • $filter 연산자는 배열 필드와 함께 작동하도록 설계됐으며 우리가 제공하는 옵션을 지정
  • $filter 의 첫 번째 옵션은 input, 단순 배열 지정
  • 필드 경로 지정자를 사용해 funding_rounds 배열 찾음

7.7 누산기(계산기)

중간 산술 논리 장치 결과가 저장되는 레지스터이다.

  • 누산기는 표현식의 유형이지만 도큐먼트에서 찾은 필드 값으로부터 값을 계산하므로 자체 클래스에서 고려한다.
  • $sum(합산), $avg(평균) 등의 작업 가능
  • $first, $last 누산기로 간주 - 표현식이 사용된 단계를 통과하는 모든 도큐먼트 값을 고려하기 때문
  • $max, $min 도큐먼트 스트림을 고려해 표시되는 값 중 하나만 저장하는 누산기
  • $mergeObjects로 여러 도큐먼트를 하나의 도큐먼트로 결합 가능
  • 배열용 누산기 - 도큐먼트가 파이프라인을 통과할 때 배열에 값을 $push 가능
    • $addToSet, $push 의 차이 - 배열에 중복값 추가되지 않는다.
  • mongoDB3.2 이전 그룹 단계($group)에서만 사용할 수 있었다.
  • mongoDB3.2는 선출 단계에서 누산기의 서브셋에 접근하는 기능을 도입했다.
  • 선출 단계에서는 $sum, $avg와 같은 누산기가 단일 도큐먼트 내 배열에서 작동
  • 그룹 단계($group)에서는 누산기가 여러 도큐먼트 값에 걸쳐 계산을 할 수 있다.
  • 그룹 단계(group),선출단계(group), 선출 단계(project)에서의 누산기 작동의 차이점이다.

7.7.1 선출 단계에서 누산기 사용

> db.companies.aggregate([
	{$match: {"funding_rounds": {$exists: true, $ne: []}}},
	{$project: {
		_id: 0,
		name: 1,
		largest_round: {$max: "$funding_rounds.raised_amount"}
	}}
])

>> { "name" : "Wetpaint", "largest_round" : 25000000 }
{ "name" : "Digg", "largest_round" : 28700000 }
{ "name" : "Facebook", "largest_ round": 1500000000 }
{ "name" : "Omnidrive", "largest_round" : 800000 } 
{ "name" : "Geni" , "largest_round" : 10000000 } 
{ "name" : "Twitter", "largest_round" : 400000000 }
{ "name" : "StumbleUpon", "largest_round" : 17000000 }
{ "name" : "Gizmoz", "largest_round" : 6500000 } 
{ "name" : "Scribd", "Largest_round" : 13000000 }
{ "name" : "Slacker", "largest_round" : 40000000 } 
{ "name" : "Lala", "Largest_round" : 20000000 }
{ "name" : "eBay", "Largest _round" : 6700000 }
{ "name" : "MeetMoi", "largest_round" : 2575000 }
{ "name" : "Joost", "Largest round" : 45000000 }
{ "name" : "Babe laum" , "largest_round": 13200000 }
{ "name" : "Plaxo", "largest_round" : 9000000 } 
{ "name" : "Cisco", "Largest _round" : 2500000 }
{ "name" : "Yahoo!", "largest_round" : 4800000 }
{ "name" : "Powerset", "largest _round" : 12500000 }
{ "name" : "Technorati", "largest_round" : 10520000 }
  • sum 누산기를 사용한 모금액 계산
> db.companies.aggregate([
	{$match: {"funding_rounds": {$exists: true, $ne: []}}},
	{$project: {
		_id: 0,
		name: 1,
		largest_round: {$sum: "$funding_rounds.raised_amount"}
	}}
])

7.8 그룹화 소개

  • 그룹 단계
    • SQL의 group by와 유사한 기능 수행

    • 여러 도큐먼트 값을 집계하고, 집계한 값에 평균 계산 같은 집계 작업 가능

      > db.companies.aggregate([
      	{$group: {
      		_id: { founded_year: "$founded_year" },
      	average_number_of_employees: {$avg: "$number_of_employess" }
      	}},
      	{$sort: { average_number_of_employees: -1 }}
      ])
    • founded_year 기준으로 모든 회사를 합친 연도마다 평균 직원 수를 계산

    • _id 로 grouping

7.8.1 그룹 단계의 _id 필드

> db.companies.aggregate([
	{$match: { founded_year: { $gte: 2013} } },
	{$group: {
		_id: {founded_year: "$founded_year"},
		companies: {$push: "$name"}
	}},
	{$sort: { "_id.founded_year": 1} }
]).pretty()
  • 출력에 _idcompanies라는 필드가 있는 도큐먼트가 있다.
  • _id내에 founded_year내에 필드를 넣은 이유
    • 그룹 값에 레이블을 지정하지 않으면 회사 설립 연도기준으로 그룹화한다는 점을 분명하게 보여주기 위함
    • 혼동을 피하기 위해선 그룹화할 값에 명시적으로 레이블 지정
> 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()
  • 도큐먼트를 그룹 단계에서 _id값으로 사용하면 문제 없다.
> db.companies.aggregate([
	{$match: { founded_year: { $gte: 2010} } },
	{$group: {
		_id: {ipo_year: "$ipo.ipo_year"},
		companies: {$push: "$name"}
	}},
	{$sort: { "_id.ipo_year": 1} }
]).pretty()
  • ipo_year기준으로 도큐먼트 그룹화 - 내장 도큐먼트 필드
  • 내장 도큐먼트 필드 경로를 그룹화할 값으로 사용하는것이 일반적

$push

  • 그룹 단계가 입력 스트림 내 도큐먼트를 처리하면, 그 결괏값을 $push표현식이 배열에 추가한다.
    db.companies.aggregate([
    	{$match: { "relationships": {$ne: null} } },
    	{$project: { relationships: 1, _id:0 } },
    	{$unwind: "$relationships" },
    	{$group: {
    		_id: "$relationships.person",
    		count: {$sum:1 }
    		}},
    		{$sort: { count: -1 }}
    ]}
  • 모르겠다

7.8.2 그룹 vs 선출

> db.companies.aggregate([
	{$match: { funding_rounds: {$ne: []} } },
	{$unwind: "$funding_rounds" },
	{$sort: { "funding_rounds.funded_year": 1,
		"funding_rounds.funded_month": 1,
		"funding_rounds.funded_day": 1} },
	{$group: {
		_id: {company: "$name"},
		funding: {
			$push: {
				amount: "$funding_rounds.raised_amount",
				year: "$funding_rounds.funded_year"
			}}
	}},
]).pretty()
  1. funding_rounds 배열이 비어있지 않은 도큐먼트를 필터링한다.
  2. funding_rounds 전개
  3. 정렬, 그룹 단계에서 funding_rounds 배열의 각 요소에 대해 도큐먼트가 표시된다.
  4. 정렬
    • 연도, 월, 일을 기준으로 오름차순
    • 가장 오래된 funding_rounds 부터 출력한다.
  5. 그룹 단계
    • 회사 이름별로 그룹화
    • $push 누산기를 통해 정렬된 funding_rounds 배열을 구성
>> 
{
	"_id": {
  • $push를 사용해 배열 누적
  • $push 표현식은 그룹 단계에서만 작동
    • 그룹 단계까 도큐먼트의 입력 스트림을 가져와 각 도큐먼트를 차례로 처리해 값을 축적하도록 설계됐기 때문
    • 선출 단계는 입력 스트림의 각 도큐먼트에 대해 개별적으로 작동한다.
db.companies.aggregate([
  { $match: { funding_rounds: { $exists: true, $ne: [ ] }} }, 
	{ $unwind: "$funding_rounds" },
	{ $sort: { "funding_rounds.funded_year": 1,
		"funding_rounds.funded_month": 1,
		"funding_rounds.funded_day": 1} },
	{$group: {
	_id: { company: "$name" },
	first_round: { $first: "$funding_rounds" },
	last_round: { $last: "$funding_rounds"},
	num_rounds: { $sum: 1},
	total_raised: { $sum: "$funding_rounds.raised_amount" }
} },
{ $project: {
	_id:0,
	company: "$_id.company",
	first_round: {
		amount: "$first_round.raised_amount",
		article: "$first_round.source_url",
		year: "$first_round.funded_year"
},
last_round: {
	amount: "$last_round.raised_amount",
	article: "$alst_round.source_url",
	year: "$last_round.funded_year"
},
num_rounds: 1,
total_raised: 1,
} },
{ $sort: { total_raised: -1 } }
]).pretty()
  • funding_rounds를 전개하고 시간 순으로 정렬
  • funding_rounds를 나타내는 배열의 항목을 누적하는 대신 $first$last라는 누산기 사용
  • $first표현식은 단계의 입력 스트림을 통과하는 첫 번째 값을 저장
  • $last 는 그룹 단계를 통과한 마지막 값을 추적
  • 선출 단계에서는 $push $first $last 사용X
  • 선출 단계는 해당 단계를 통해 스트리밍되는 도큐먼트를 기반으로 값을 누적하도록 설계되지 않았기 때문

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

  • 집계 파이프라인에서 생성된 도큐먼트를 컬렉션에 사용할 수 있는 두 가지 단계로 $out $merge - 둘 중 하나, 마지막 단계
  • $merge mongoDB 4.2 도입 - 쓰기 단계에서 선호
  • $out
    • 동일한 db에서 사용
    • 기존 컬렉션이 있다면 오버라이딩
    • 샤딩된 컬렉션에 사용X
  • $merge
    • 샤딩 여부 관계 없이 모든 데이터베이스와 컬렉션에 쓸 수 있다.
    • 기존 컬렉션에 작업할 때 결과를 통합 할 수 있다.
    • 파이프라인 실행 시 출력 컬렉션의 내용이 점신적으로 갱신 - 주문식의 구체화된 뷰를 생성할 수 있다.
profile
이제부터 백엔드 개발자

0개의 댓글