MongoDB에서 두 컬렉션을 하나로 묶어 처리할 때, 한 컬렉션의 특정 필드 값이 다른 컬렉션의 대응되는 값보다 큰 문서만 골라내야 하는 경우가 있습니다. 이런 조건 비교는 단순히 $lookup만으로는 처리할 수 없으며, $unwind와 $redact를 함께 사용해야 합니다. 아래에서 예제를 통해 전체 과정을 단계별로 살펴보겠습니다.
첫 번째 컬렉션 생성하기
먼저 제품 이름(ProductName)과 가격(ProductPrice)을 담은 demo446 컬렉션을 생성합니다.
> db.demo446.insert([
... { "ProductName": "Product1", "ProductPrice": 60 },
... { "ProductName": "Product2", "ProductPrice": 90 }
... ])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})find() 메서드를 사용해 컬렉션의 모든 문서를 조회합니다.
> db.demo446.find();
위 명령은 다음과 같은 결과를 출력합니다.
{ "_id" : ObjectId("5e790766bbc41e36cc3caec3"), "ProductName" : "Product1", "ProductPrice" : 60 }
{ "_id" : ObjectId("5e790766bbc41e36cc3caec4"), "ProductName" : "Product2", "ProductPrice" : 90 }두 번째 컬렉션 생성하기
비교 대상이 될 demo447 컬렉션도 동일한 구조로 생성합니다.
> db.demo447.insert([
...
... { "ProductName": "Product1", "ProductPrice": 40 },
... { "ProductName": "Product2", "ProductPrice": 70 }
... ])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})마찬가지로 find() 메서드로 저장된 문서를 확인합니다.
> db.demo447.find();
위 명령은 다음과 같은 결과를 출력합니다.
{ "_id" : ObjectId("5e790789bbc41e36cc3caec5"), "ProductName" : "Product1", "ProductPrice" : 40 }
{ "_id" : ObjectId("5e790789bbc41e36cc3caec6"), "ProductName" : "Product2", "ProductPrice" : 70 }집계 파이프라인 작성하기
이제 한 컬렉션의 ProductPrice가 다른 컬렉션의 가격에 배율(rate)을 곱한 값보다 클 때만 문서를 남기는 집계 쿼리를 작성합니다. 여기서는 rate 변수를 1로 설정했습니다.
- $match: ProductPrice와 ProductName 필드가 실제로 존재하는 문서만 통과시켜 불필요한 연산을 줄입니다.
- $lookup: ProductName 값을 기준으로 demo447 컬렉션과 조인하여 일치하는 문서를 demo447 배열 필드에 담습니다.
- $unwind: 조인 결과로 생긴 배열을 개별 문서로 풀어냅니다.
- $redact: 첫 번째 컬렉션의 가격이 상대 컬렉션 가격 × rate + 3보다 크면
$$KEEP으로 문서를 유지하고, 그렇지 않으면$$PRUNE으로 제거합니다.
> var rate = 1;
> db.demo446.aggregate([
... { "$match": { "ProductPrice": { "$exists": true }, "ProductName": { "$exists": true } } },
... {
... "$lookup": {
... "from": "demo447",
... "localField": "ProductName",
... "foreignField": "ProductName",
... "as": "demo447"
... }
... },
... { "$unwind": "$demo447" },
... {
... "$redact": {
... "$cond": [
... {
... "$gt": [
... "$ProductPrice", {
... "$add": [
... { "$multiply": [ "$demo447.ProductPrice",rate ] },
... 3
... ]
... }
... ]
... },
... "$$KEEP",
... "$$PRUNE"
... ]
... }
... }
... ])위 파이프라인을 실행하면 다음과 같은 결과가 출력됩니다.
{ "_id" : ObjectId("5e790766bbc41e36cc3caec3"), "ProductName" : "Product1", "ProductPrice" : 60, "demo447" : { "_id" : ObjectId("5e790789bbc41e36cc3caec5"), "ProductName" : "Product1", "ProductPrice" : 40 } }
{ "_id" : ObjectId("5e790766bbc41e36cc3caec4"), "ProductName" : "Product2", "ProductPrice" : 90, "demo447" : { "_id" : ObjectId("5e790789bbc41e36cc3caec6"), "ProductName" : "Product2", "ProductPrice" : 70 } }정리
이처럼 $lookup으로 두 컬렉션을 조인한 뒤 $unwind로 배열을 해제하고, $redact의 조건식으로 가격을 비교하면 한 컬렉션의 필드 값이 다른 컬렉션보다 큰 문서만 손쉽게 집계할 수 있습니다. 참고로 최신 MongoDB 버전에서는 $redact 대신 $match와 $expr을 조합해 동일한 결과를 더 간결하게 얻을 수도 있습니다.