집계
- MongoDB는 집계를 수행하는 세 가지 방법을 제공
- 집계 파이프라인
- Map-reduce(5.0 버전부터 사용 X)
- 단일 목적 집계 연산
1. 집계 프레임워크 개요
1.1 집계 프레임워크 개요
- 파이프라인의 각 단계에서의 출력이 다음 단계로의 입력으로 제공되는 집계 파이프라인을 정의
- 집계 파이프라인 작업 요약
- $project: 출력 도큐먼트상에 배치할 피드를 지정
- $match: 처리될 도큐먼트를 선택하는 것(find() 와 비슷한 역할을 수행)
- $limit: 다음 단계에 전달될 도큐먼트의 수를 제한
- $skip: 지정된 수의 도큐먼트를 건너뜀
- $unwind: 배열을 확장하여 각 배열 항목에 대해 하나의 출력 도큐먼트를 생성
- $group: 지정된 키로 도큐먼트를 그룹화
- $sort: 도큐먼트를 정렬
- $geoNear: 지리 공간위치 근처의 도큐먼트를 선택
- $out: 파이프라인의 결과(출력)를 컬렉션에 추가
- $redact: 특정 데이터에 대한 접근을 제어
- 집계 파이프라인 예
db.products.aggregate([ // 1. 전체 products 전달
{$match: ...}, // 2. 처리할 도큐먼트를 선택 및 결과 전달
{$group: ...}, // 3. 특정 키 기준으로 도큐먼트 그룹화 및 결과 전달
{$sort: ...} // 4. 도큐먼트를 정렬 및 최종 결과 반환
])
| SQL 명령어 |
집계 프레임워크 연산자 |
| SELECT |
$project / $group 함수: $sum, $min, $avg 등 |
| FROM |
db.collectionName.aggregate(...) |
| JOIN |
$unwind |
| WHERE |
$match |
| GROUP BY |
$group |
| HAVING |
$match |
2. 전자상거래 집계 예제
- 예시 전자상거래 데이터모델
- products: 제품
- reviews: 리뷰
- categories: 카테고리
- orders: 주문
2.1. 상품, 카테고리, 리뷰
리뷰 수 조회 예시
// 쿼리
product = db.products.findOne( {'slug': 'wheelbarrow-9092'});
reviews_count = db.reviews.count( {'product_id': product['_id']} );
// 집계
db.reviews.aggregate([
{
$group : { _id:'$product_id', // product_id 로 그룹화
count:{$sum:1} } // 각 제품에 대한 리뷰 수 카운트
}
]);
하나의 상품에 대한 리뷰 수 조회 예시
product = db.products.findOne({'slug': 'wheelbarrow-9092'});
// 쿼리
db.reviews.count({'product_id': product['_id']});
// 집계
ratingSummary = db.reviews.aggregate([
{$match : { product_id: product['_id']} }, // 하나의 상품 _id 선택
{$group : { _id:'$product_id', // 결과의 첫번째 도큐먼트 반환
count:{$sum:1} }}
]).next();
평균 리뷰 계산
product = db.products.findOne({'slug': 'wheelbarrow-9092'});
ratingSummary = db.reviews.aggregate([
{$match : {'product_id': product['_id']}},
{$group : { _id:'$product_id',
average:{$avg:'$rating'}, // 제품 평균 평점 계산
count: {$sum:1}}}
]).next();
등급별 리뷰 계산
// 집계
countsByRating = db.reviews.aggregate([
{$match : {'product_id': product['_id']}}, // 제품 선택
{$group : { _id:'$rating', // rating 값별로 그룹화
count:{$sum:1}}} // 각 등급별 리뷰 수 계산
]).toArray(); // 결과 커서 배열 반환
// SQL
SELECT RATING, COUNT(*) AS COUNT
FROM REVIEWS
WHERE PRODUCT_ID = '4c4b1476238d3b4dd5003981'
GROUP BY RATING
컬렉션 조인
- 각각의 주요 카테고리의 제품 수 계산
- 제품과 카테고리는 1:1 관계
- 예시
// 집계
db.products.aggregate([
{$group : { _id:'$main_cat_id',
count:{$sum:1}}}
]);
// 결과
{ "_id" : ObjectId("6a5b1476238d3b4dd5000048"), "count" : 2 }
- 의사 조인(pseudo-join)
- 조회 및 가공 후 요약 컬렉션에 저장하여 조인된 결과와 같이 생성
- 2.6부터 기능 지원
- 시간이 오래 걸리 수 있음
db.mainCategorySummary.remove({}); // mainCategorySummary 컬렉션에서 기존 도큐먼트 제거
db.products.aggregate([
{$group : { _id:'$main_cat_id',
count:{$sum:1}}}
]).forEach(function(doc){
var category = db.categories.findOne({_id:doc._id}); // 결과에 대한 카테고리 조회
if (category !== null) { // 카테고리가 실제로 존재한다는 보장 X
doc.category_name = category.name;
}
else {
doc.category_name = 'not found';
}
db.mainCategorySummary.insert(doc); // 결합된 결과를 요약 컬렉션에 삽입
})
$out과 $project
db.mainCategorySummary.insert(doc); // 결합된 결과를 요약 컬렉션에 삽입
// $out
db.products.aggregate([
{$group : { _id:'$main_cat_id',
count:{$sum:1}}},
{$out : 'mainCategorySummary'} // 파이프라인 결과를
]) // mainCategorySummary 컬렉션에 저장
- $product
- 파이프라인의 다음 단계로 절달할 필드를 필터링
db.products.aggregate([
... {$project : {category_ids:1}} // category_ids
... ]);
// 결과 category_ids로 전달 필드 제한
{ "_id" : ObjectId("4c4b1476238d3b4dd5003981"),
"category_ids" : [ ObjectId("6a5b1476238d3b4dd5000048"),
ObjectId("6a5b1476238d3b4dd5000049") ] }
{ "_id" : ObjectId("4c4b1476238d3b4dd5003982"),
"category_ids" : [ ObjectId("6a5b1476238d3b4dd5000048"),
ObjectId("6a5b1476238d3b4dd5000049") ] }
$unwind 로 더 빨라진 조인
- 배열을 확장하여 모든 입력 도큐먼트 배열 항목에 대해 하나의 출력 도큐먼트를 생성
- 하위 도큐먼트가 나타날 떄마다 도큐먼트를 조인 가능
db.products.aggregate([
{$project : {category_ids:1}}, // category_ids 만 다음으로 전달
{$unwind : '$category_ids'}, // category_ids 의
{$group : { _id:'$category_ids', // 모든 배열 항목에 대한 출력 도큐먼트 생성
count:{$sum:1}}},
{$out : 'countsByCategory'} // 집계 결과를 컬렉션에 저장
]);
2.2. 사용자와 주문
연도별, 원별 판매 요약
db.orders.aggregate([
{$match: {purchase_data: {$gte: new Date(2010, 0, 1)}}}, // 날짜 이후 데이터
{$group: {
_id: {year : {$year :'$purchase_data'}, // year, month 복합키
month: {$month :'$purchase_data'}},
count: {$sum:1}, // 주문 수
total: {$sum:'$sub_total'}}}, // 주문 총 합계
{$sort: {_id:-1}}
]);
맨해튼 최고의 고객 찾기
- 북부 맥해튼에서 가장 높은 지출을 보이는 소비자 찾는 쿼리 예제
- 쿼리 구성
- $match: Upper Manhattan으로 선적된 주문 찾기
- $group: 각 고객별 주문 금액을 합산
- $match: 주문 총액이 $100보다 큰 고객 선택
- $sort: 고객 주문 합계 금액으로 결과 내림차순 정렬
// 각 단계로 전달할 매개변수 정의
upperManhattanOrders = {'shipping_address.zip': {$gte: 10019, $lt: 10040}};
sumByUserId = {_id: '$user_id',
total: {$sum:'$sub_total'}, };
orderTotalLarge = {total: {$gt:10000}};
sortTotalDesc = {total: -1};
// 집계
db.orders.aggregate([
{$match: upperManhattanOrders},
{$group: sumByUserId},
{$match: orderTotalLarge},
{$sort: sortTotalDesc}
]);
// 테스트
db.orders.aggregate([
{$group: sumByUserId},
{$match: orderTotalLarge},
{$limit: 10}
]);
// 테스트 - 주문수 유지 결정
sumByUserId = {_id: '$user_id',
total: {$sum:'$sub_total'},
count: {$sum: 1}};
// $out으로 저장
db.orders.aggregate([
{$match: upperManhattanOrders},
{$group: sumByUserId},
{$match: orderTotalLarge},
{$sort: sortTotalDesc},
{$out: 'targetedCustomers'}
]);
3. 집계 파이프라인 연산자
- 집계 프레임워크는 10개 연산자 지원
- $project: 처리할 도큐먼트 필드 지정
- $group: 지정된 키로 도큐먼트를 그룹화
- $addToSet: 그룹에 고유한 값의 배열 생성(중복 값 X)
- $first: 그룹의 첫번째 값($sort 선행 필요)
- $last: 그룹의 마지막 값($sort 선행 필요)
- $max: 그룹의 필드 최대값
- $min: 그룹의 필드 최소값
- $avg: 필드의 평균값
- $push: 그룹의 모든 값의 배열을 반환(중복 값 포함)
- $sum: 그룹의 모든 값의 합계
- $match: 처리될 도큐먼트를 선택하는 것(find() 와 비슷한 역할을 수행)
- $limit: 다음 단계에 전달될 도큐먼트의 수를 제한
- $skip: 지정된 수의 도큐먼트를 건너뜀
- $unwind: 배열을 확장하여 각 배열 항목에 대해 하나의 출력 도큐먼트를 생성
- $sort: 도큐먼트를 정렬
- $geoNear: 지리 공간위치 근처의 도큐먼트를 선택
- $out: 파이프라인의 결과(출력)를 컬렉션에 추가
- $redact: 특정 데이터에 대한 접근을 제어