MongoDB 를 사용하여 쿼리를 하던 중에 쿼리 최적화를 위해서 관련있는 여러 컬렉션간의 조인에 대한 필요성이 발생했다. 그런데 MongoDB 는 NoSQL 데이터베이스이기 때문에 RDB 와 다르게 테이블 개념이 아니고 FK 등을 통해 테이블 간의 관계가 정의되어 있지 않아서 어떻게 조인을 하는지 검색해보았다. 검색 중에 MongoDB 문서에서 Aggregation 에 대한 정보를 찾았고 오늘은 이에 대해서 정리해보려 한다.
1. Aggregation
Aggregation 은 여러 document 들을 연산하여 나온 결과물을 반환하는 연산이다. RDB 에서 조인을 통해 여러 테이블의 데이터를 합쳐서 반환하는 것처럼 aggregation 도 여러 document 의 값들을 조건에 맞게 묶어서 그룹화한 결과를 반환한다.
mongo db client 에서 aggregation 을 사용하는 방법은 두가지이다. 직접 pipeline 의 각 스테이지별 쿼리를 입력하여 실행하는 aggregation pipeline 과 이미 구현된 함수들을 사용하여 하나의 기능을 수행하도록 하는 single purpose aggregation methods 가 있다.
- Aggregation Pipelines
Aggregation pipeline 은 document 를 처리하는 여러가지 스테이지로 구분되어 있다. 각 스테이지는 입력된 document 들에 대하여 필터링, 그룹화, 값들의 연산 등의 동작을 수행한다. 각 스테이지의 결과는 다음 스테이지의 입력값으로 넘어간다. aggregation pipeline 의 결과는 총합, 평균, 최대값, 최소값 등 document 들의 그룹화한 결과로 반환될 수 있다.
아래는 aggregation 을 사용한 코드 예제이다. 아래는 두개의 pipeline 을 가지고 있는데, 첫번째는 $match 스테이지로 size 가 medium 인 document 만 필터링한다. 두번째 스테이지는 $group 스테이지로 첫번째 스테이지에서 필터링된 document 들을 name 으로 묶고 quantity 의 총합을 totalQuantity 에 저장하여 반환한다.
db.orders.aggregate( [
// Stage 1: Filter pizza order documents by pizza size
{
$match: { size: "medium" }
},
// Stage 2: Group remaining documents by pizza name and calculate total quantity
{
$group: { _id: "$name", totalQuantity: { $sum: "$quantity" } }
}
] )
- Single Purpose Aggregation Methods
이 방식은 하나의 collection 에서 docuement 들을 aggregation 하기 위해서 사용하는 방법이다. 이 방식은 간단하지만 aggregation pipeline 에 비해 수행할 수 있는 기능이 한정적이다.
메서드들의 종류는 다양한다. 대표적으로 다음과 같은 메서드들이 있다.
db.collection.count() | 해당 collection 이나 view 에 있는 docuement 들의 개수를 반환한다. |
db.collection.distinct() | 지정된 필드에 대해서 distinct 한 값을 가지고 있는 docuement 들을 반환한다. |
2. Aggregation Pipeline Stages
Aggregation pipeline 은 다양한 스테이지 연산자들을 가진다. 그 중에서 대표적인 stage 들을 정리한다.
- $count
해당 스테이지의 결과 document 들의 개수를 반환한다.
pipeline 에서 사용될때 {$count: <string>} 의 형태로 사용되며 <string> 에는 $count 의 결과값을 할당할 필드의 이름을 입력한다. 이때 필드의 이름은 $ 로 시작하거나 . 을 포함해서는 안된다.
db.scores.aggregate(
[
{
$match: {
score: {
$gt: 80
}
}
},
{
$count: "passing_scores"
}
]
)
위의 쿼리는 score 가 80 이상인 document 의 개수를 반환한다.
위 쿼리의 결과값은 아래와 같은 형식으로 passing_scores 필드에 count 값이 입력되어 반환된다.
{"passing_scores": 4 }
- $group
$group 스테이지는 group key 를 기준으로 document 들을 구분한다. 구분된 document 들은 각 고유한 group key 별로 하나의 document 로 출력된다.
db.sales.aggregate( [
{
$group: {
_id: null,
count: { $count: { } }
}
}
] )
$group 스테이지를 group key 는 _id 에 입력되는 값을 사용하여 grouping 을 수행한다. 이때 _id 에 null 을 입력하면 아래와 같은 전체 document 들에 대한 grouping 이 수행된다.
{'_id': null, 'count': 1000}
만약 null 이 아닌 document 의 key 를 입력하면 아래와 같이 실제 key 를 기준으로 grouping 한 결과를 출력한다.
{'_id': 'A', 'count': 533}, {'_id': 'B', 'count': 467}
- $match
$match 스테이지에서는 입력된 조건을 통과하는 document 들을 결과로 반환하는 필터링을 수행한다.
db.articles.aggregate(
[ { $match : { author : "dave" } } ]
)
위의 예제를 실행하면 document 들 중에서 author 가 dave 인 document 들만 결과로 반환한다.
- $lookup
$lookup 은 같은 database 에 저장된 collection 들에 대하여 left outer join 을 수행하는 스테이지이다. $lookup 스테이지는 join 된 컬렉션의 document 를 포함하는 새로운 배열 필드를 추가한 결과를 다음 스테이지로 넘긴다.
db.col.aggregate(
[
{$lookup: {
from: "COLLECTION_NAME",
localField: "FIELD_FROM_INPUT_DOCS",
foreignField: "FIELD_FROM_JOINED_DOCS",
as: "OUTPUT_ARRAY_FIELD"
}
}
]
)
$lookup 스테이지는 from 을 통해서 join 할 document 의 collection 을 입력받고 두 document 를 join 할 키를 각각 localField 와 foreignField 로 입력받는다. localField 는 이전 스테이지에서 input 으로 넘어온 docuement 들의 필드 키이고 foreignField 는 ㅓjoin 을 위해 from 에서 입력한 collection 의 docuement 의 필드 키를 입력받는다. 이 두개의 키로 join 한 결과는 "as" 에서 정의한 필드 키에 저장되어 결과로 출력된다.
이 외에도 다양한 스테이지들이 존재하는데 이들은 다음 링크의 mongo db document 에서 확인할 수 있다.
- https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/
[Reference]
- https://www.mongodb.com/docs/manual/aggregation/https://www.mongodb.com/docs/manual/aggregation/
- https://www.mongodb.com/docs/manual/reference/method/db.collection.aggregate/
'기타' 카테고리의 다른 글
[FastAPI] BackgroundTasks (0) | 2023.05.26 |
---|---|
[NGINX] NGINX 란? (1) | 2023.05.11 |
[Gradle] Gradle 사용법 - 설치, 초기화 및 (1) | 2023.01.17 |
[Gradle] Gradle 이란? (1) | 2023.01.14 |
[기타] Sync - Async / Blocking - Non-Blocking (0) | 2022.04.05 |