문서 메뉴
문서 홈
/
MongoDB 매뉴얼
/ /

맵-리듀스 예제

참고

맵 리듀스의 대안으로서의 집계 파이프라인

집계 파이프라인맵 리듀스 작업보다 더 나은 성능과 유용성을 제공합니다.

맵 리듀스 작업은 $group$merge애그리게이션 파이프라인 단계를 사용해 다시 작성할 수 있습니다.

사용자 지정 기능이 필요한 맵 리듀스 작업의 경우 MongoDB는 $accumulator$function 집계 연산자를 제공합니다. 이러한 연산자를 사용하여 JavaScript에서 사용자 지정 집계 표현식을 정의할 수 있습니다.

mongosh 에서 db.collection.mapReduce() 메서드는 mapReduce 명령을 감싸는 래퍼입니다. 다음 예제에서는 db.collection.mapReduce() 메서드를 사용합니다.

이 섹션의 예제는 사용자 지정 집계 표현식이 없는 집계 파이프라인 대안을 제시합니다.사용자 정의 표현식을 사용하는 대안에 대해서는 맵 리듀스에서 집계 파이프라인으로 변환 예제를 참조하세요.

이 문서로 샘플 컬렉션 orders을 만드세요.

db.orders.insertMany([
{ _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" },
{ _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" },
{ _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"},
{ _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }
])

orders 컬렉션에서의 맵 리듀스 연산을 수행하여 cust_id별로 그룹화합니다. 그리고 각 cust_id에 대한 price의 합계를 계산하세요.

  1. 각 입력 문서를 처리할 맵 함수를 다음과 같이 정의하세요.

    • 이 함수에서 this는 맵 리듀스 작업이 처리하고 있는 문서를 나타냅니다.

    • 이 함수는 각 문서에 대해 pricecust_id에 매핑하고 cust_idprice을 출력합니다.

    var mapFunction1 = function() {
    emit(this.cust_id, this.price);
    };
  2. 두 개의 인수 keyCustIdvaluesPrices을 사용하여 상응하는 리듀스 함수를 정의하세요.

    • valuesPrices는 맵 함수에서 발생한 price 값들로 구성된 배열이며, 이 값들은 keyCustId로 그룹화되어 있습니다.

    • 이 함수는 valuesPrice 배열을 해당 요소의 합으로 리듀스합니다.

    var reduceFunction1 = function(keyCustId, valuesPrices) {
    return Array.sum(valuesPrices);
    };
  3. mapFunction1 맵 함수와 reduceFunction1 리듀스 함수를 사용하여 orders 컬렉션의 모든 문서에 대해 맵-리듀스를 수행하세요.

    db.orders.mapReduce(
    mapFunction1,
    reduceFunction1,
    { out: "map_reduce_example" }
    )

    이 연산은 map_reduce_example이라는 컬렉션에 해당 결과를 출력합니다. map_reduce_example 컬렉션이 이미 존재하는 경우 이 연산은 해당 콘텐츠를 이 맵-리듀스 작업의 결과로 바꿉니다.

  4. map_reduce_example 컬렉션을 쿼리하여 결과를 확인합니다.

    db.map_reduce_example.find().sort( { _id: 1 } )

    이 작업은 다음 문서를 반환합니다.

    { "_id" : "Ant O. Knee", "value" : 95 }
    { "_id" : "Busby Bee", "value" : 125 }
    { "_id" : "Cam Elot", "value" : 60 }
    { "_id" : "Don Quis", "value" : 155 }

사용 가능한 집계 파이프라인 연산자를 사용하면 사용자 지정 함수를 정의하지 않고도 맵-리듀스 작업을 다시 작성할 수 있습니다.

db.orders.aggregate([
{ $group: { _id: "$cust_id", value: { $sum: "$price" } } },
{ $out: "agg_alternative_1" }
])
  1. $group 단계는 cust_id으로 그룹화하고 value 필드를 계산합니다 ($sum도 참조하세요). value 필드에는 각 cust_id의 총 price가 포함되어 있습니다.

    이 단계에서는 다음 문서를 다음 단계로 출력합니다.

    { "_id" : "Don Quis", "value" : 155 }
    { "_id" : "Ant O. Knee", "value" : 95 }
    { "_id" : "Cam Elot", "value" : 60 }
    { "_id" : "Busby Bee", "value" : 125 }
  2. 그런 다음 $out은 결과를 agg_alternative_1 컬렉션에 씁니다. $out 대신 $merge를 사용할 수도 있습니다.

  3. agg_alternative_1 컬렉션을 쿼리하여 결과를 확인합니다.

    db.agg_alternative_1.find().sort( { _id: 1 } )

    이 작업은 다음 문서를 반환합니다.

    { "_id" : "Ant O. Knee", "value" : 95 }
    { "_id" : "Busby Bee", "value" : 125 }
    { "_id" : "Cam Elot", "value" : 60 }
    { "_id" : "Don Quis", "value" : 155 }

다음도 참조하세요.

사용자 정의 집계 표현식을 사용하는 대안은 맵-리듀스에서 집계 파이프라인으로 변환 예제를 참조하세요.

다음 예제에서는 ord_date 값이 2020-03-01 이상인 모든 문서에 대한 orders 컬렉션에서의 대한 맵-리듀스 연산을 볼 수 있습니다.

해당 예제에서의 연산은 다음과 같습니다.

  1. item.sku 필드별로 그룹화하고 각 sku에 대해 주문 수와 총 주문 수량을 계산합니다.

  2. sku 값에 대한 주문당 평균 수량을 계산하고 결과를 출력 컬렉션에 병합합니다.

결과를 병합할 때 기존 문서에 새 결과와 동일한 키가 있으면 작업이 기존 문서를 덮어씁니다. 동일한 키를 가진 기존 문서가 없는 경우에는 해당 문서가 삽입됩니다.

예시 단계:

  1. 각 입력 문서를 처리할 맵 함수를 다음과 같이 정의하세요.

    • 이 함수에서 this는 맵 리듀스 작업이 처리하고 있는 문서를 나타냅니다.

    • 함수는 각 항목에 대해 sku를 새로운 value 객체에 연결합니다. 이 객체에는 count1과 주문에 대한 항목 qty가 포함되어 있으며, sku (key 에 저장됨)와 value를 내보냅니다.

    var mapFunction2 = function() {
    for (var idx = 0; idx < this.items.length; idx++) {
    var key = this.items[idx].sku;
    var value = { count: 1, qty: this.items[idx].qty };
    emit(key, value);
    }
    };
  2. 두 개의 인수 keySKUcountObjVals을 사용하여 상응하는 리듀스 함수를 정의하세요.

    • countObjVals 맵 함수가 리듀서 함수에 전달한 그룹화된 keySKU 값에 매핑된 객체를 가지고 있는 배열입니다.

    • 이 함수는 countObjVals 배열을 countqty 필드를 포함하는 단일 객체 reducedValue로 줄입니다.

    • reducedVal 에서 count 필드는 개별 배열 요소의 count 필드 합계를 포함하고 qty 필드는 개별 배열 요소의 qty 필드 합계를 포함합니다.

    var reduceFunction2 = function(keySKU, countObjVals) {
    reducedVal = { count: 0, qty: 0 };
    for (var idx = 0; idx < countObjVals.length; idx++) {
    reducedVal.count += countObjVals[idx].count;
    reducedVal.qty += countObjVals[idx].qty;
    }
    return reducedVal;
    };
  3. 두 개의 인수 keyreducedVal을 사용하여 최종 함수를 정의합니다. 이 함수는 reducedVal 객체를 수정하여 avg라는 계산된 필드를 추가하고 수정된 객체를 반환합니다.

    var finalizeFunction2 = function (key, reducedVal) {
    reducedVal.avg = reducedVal.qty/reducedVal.count;
    return reducedVal;
    };
  4. mapFunction2, reduceFunction2finalizeFunction2 함수를 사용하여 orders 컬렉션에 대해 맵-리듀스 작업을 수행하세요.

    db.orders.mapReduce(
    mapFunction2,
    reduceFunction2,
    {
    out: { merge: "map_reduce_example2" },
    query: { ord_date: { $gte: new Date("2020-03-01") } },
    finalize: finalizeFunction2
    }
    );

    이 작업은 query 필드를 사용하여 ord_datenew Date("2020-03-01") 이상인 문서만 선택합니다. 그런 다음 결과를 컬렉션 map_reduce_example2에 출력합니다.

    map_reduce_example2 컬렉션이 이미 존재하는 경우 작업은 기존 콘텐츠를 이 맵-리듀스 연산의 결과와 병합합니다. 즉, 기존 문서에 새 결과와 동일한 키가 있는 경우 연산은 기존 문서를 덮어씁니다. 동일한 키를 가진 기존 문서가 없는 경우 해당 문서가 삽입됩니다.

  5. map_reduce_example2 컬렉션을 쿼리하여 결과를 확인합니다.

    db.map_reduce_example2.find().sort( { _id: 1 } )

    이 작업은 다음 문서를 반환합니다.

    { "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } }
    { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } }
    { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } }
    { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } }
    { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }

사용 가능한 집계 파이프라인 연산자를 사용하면 사용자 지정 함수를 정의하지 않고도 맵-리듀스 작업을 다시 작성할 수 있습니다.

db.orders.aggregate( [
{ $match: { ord_date: { $gte: new Date("2020-03-01") } } },
{ $unwind: "$items" },
{ $group: { _id: "$items.sku", qty: { $sum: "$items.qty" }, orders_ids: { $addToSet: "$_id" } } },
{ $project: { value: { count: { $size: "$orders_ids" }, qty: "$qty", avg: { $divide: [ "$qty", { $size: "$orders_ids" } ] } } } },
{ $merge: { into: "agg_alternative_3", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
] )
  1. $match 단계에서는 ord_datenew Date("2020-03-01") 이상인 문서만 선택합니다.

  2. $unwind 단계에서는 items 배열 필드별로 문서를 세분화하여 각 배열 요소에 대한 문서를 출력합니다. 예를 들면 다음과 같습니다.

    { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 5, "price" : 2.5 }, "status" : "A" }
    { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "apples", "qty" : 5, "price" : 2.5 }, "status" : "A" }
    { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "oranges", "qty" : 8, "price" : 2.5 }, "status" : "A" }
    { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" }
    { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "pears", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 4, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-18T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 5, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-19T00:00:00Z"), "price" : 50, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" }
    ...
  3. $group 단계는 items.sku를 기준으로 그룹화하여 각 sku에 대해 다음과 같이 계산합니다.

    • qty 필드. qty 필드는 다음을 포함합니다.
      items.sku에 대한 총 qty ($sum 참조).
    • orders_ids 배열. orders_ids 필드는 다음을 포함합니다.
      items.sku에 대한 고유 주문 _id 배열 ($addToSet참조).
    { "_id" : "chocolates", "qty" : 15, "orders_ids" : [ 2, 5, 8 ] }
    { "_id" : "oranges", "qty" : 63, "orders_ids" : [ 4, 7, 3, 2, 9, 1, 10 ] }
    { "_id" : "carrots", "qty" : 15, "orders_ids" : [ 6, 9 ] }
    { "_id" : "apples", "qty" : 35, "orders_ids" : [ 9, 8, 1, 6 ] }
    { "_id" : "pears", "qty" : 10, "orders_ids" : [ 3 ] }
  4. $project 단계에서는 맵 리듀스의 출력을 미러링하여 _idvalue 두 필드를 갖도록 출력 문서를 재구성합니다. $project는 다음과 같이 설정합니다.

  5. $unwind 단계에서는 items 배열 필드별로 문서를 세분화하여 각 배열 요소에 대한 문서를 출력합니다. 예를 들면 다음과 같습니다.

    { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 5, "price" : 2.5 }, "status" : "A" }
    { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-01T00:00:00Z"), "price" : 25, "items" : { "sku" : "apples", "qty" : 5, "price" : 2.5 }, "status" : "A" }
    { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "oranges", "qty" : 8, "price" : 2.5 }, "status" : "A" }
    { "_id" : 2, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 70, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" }
    { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 3, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-08T00:00:00Z"), "price" : 50, "items" : { "sku" : "pears", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 4, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-18T00:00:00Z"), "price" : 25, "items" : { "sku" : "oranges", "qty" : 10, "price" : 2.5 }, "status" : "A" }
    { "_id" : 5, "cust_id" : "Busby Bee", "ord_date" : ISODate("2020-03-19T00:00:00Z"), "price" : 50, "items" : { "sku" : "chocolates", "qty" : 5, "price" : 10 }, "status" : "A" }
    ...
  6. $group 단계는 items.sku를 기준으로 그룹화하여 각 sku에 대해 다음과 같이 계산합니다.

    • qty 필드.qty 필드에는 $sum을 사용하여 계산된 각 items.sku 별 주문된 총 qty가 포함되어 있습니다.

    • orders_ids 배열. orders_ids 필드는 $addToSet을 사용하여 계산된 items.sku의 고유한 주문 _id 배열을 포함하고 있습니다.

    { "_id" : "chocolates", "qty" : 15, "orders_ids" : [ 2, 5, 8 ] }
    { "_id" : "oranges", "qty" : 63, "orders_ids" : [ 4, 7, 3, 2, 9, 1, 10 ] }
    { "_id" : "carrots", "qty" : 15, "orders_ids" : [ 6, 9 ] }
    { "_id" : "apples", "qty" : 35, "orders_ids" : [ 9, 8, 1, 6 ] }
    { "_id" : "pears", "qty" : 10, "orders_ids" : [ 3 ] }
  7. $project 단계에서는 맵 리듀스의 출력을 미러링하여 _idvalue 두 필드를 갖도록 출력 문서를 재구성합니다. $project는 다음과 같이 설정합니다.

    • $size 를 사용하여 value.countorders_ids 배열의 크기로 설정합니다.

    • value.qty를 입력 문서의 qty 필드로 설정합니다.

    • $divide$size를 사용하여 value.avg를 주문당 평균 주문 수량으로 설정합니다.

    { "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } }
    { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }
    { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } }
    { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } }
    { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } }
  8. 마지막으로 $merge는 출력을 컬렉션 agg_alternative_3에 씁니다. 기존 문서에 새 결과와 동일한 키 _id가 있는 경우 작업은 기존 문서를 덮어씁니다. 동일한 키를 가진 기존 문서가 없는 경우 작업이 문서를 삽입합니다.

  9. agg_alternative_3 컬렉션을 쿼리하여 결과를 확인합니다.

    db.agg_alternative_3.find().sort( { _id: 1 } )

    이 작업은 다음 문서를 반환합니다.

    { "_id" : "apples", "value" : { "count" : 4, "qty" : 35, "avg" : 8.75 } }
    { "_id" : "carrots", "value" : { "count" : 2, "qty" : 15, "avg" : 7.5 } }
    { "_id" : "chocolates", "value" : { "count" : 3, "qty" : 15, "avg" : 5 } }
    { "_id" : "oranges", "value" : { "count" : 7, "qty" : 63, "avg" : 9 } }
    { "_id" : "pears", "value" : { "count" : 1, "qty" : 10, "avg" : 10 } }

다음도 참조하세요.

사용자 정의 집계 표현식을 사용하는 대안은 맵-리듀스에서 집계 파이프라인으로 변환 예제를 참조하세요.

돌아가기

동시성