Docs Menu
Docs Home
/
MongoDBマニュアル
/ /

map-reduce の例

注意

map-reduce の代替としての集計パイプライン

集計パイプラインは、 map-reduce操作よりも優れたパフォーマンスと使いやすさを提供します。

map-reduce 操作は、 $group$mergeなどの集計パイプラインステージを使用して書き換えることができます。

カスタム機能を必要とする map-reduce 操作の場合、MongoDB は$accumulator$functionの集計演算子を提供します。 これらの演算子を使用して、JavaScript でカスタム集計式を定義します。

mongoshでは、 db.collection.mapReduce()メソッドはmapReduceコマンドのラッパーです。 次の例ではdb.collection.mapReduce()メソッドを使用します。

このセクションの例には、カスタム集計式を使用しない集計パイプラインの代替手段が含まれています。カスタム式を使用する代替方法については「map-reduce から集計パイプラインへの変換例」を参照してください。

次のドキュメントを含むサンプル コレクション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 コレクションに対して map-reduce 操作を実行して、cust_idごとにグループ化し、各cust_idprice の合計値を計算します。

  1. 各入力ドキュメントを処理する map 関数を定義します。

    • この関数では、 this は map-reduce 操作で処理中のドキュメントを参照します。

    • この関数は、各ドキュメントのpricecust_idにマッピングし、 cust_idpriceを出力します。

    var mapFunction1 = function() {
    emit(this.cust_id, this.price);
    };
  2. 2つの引数keyCustIdvaluesPricesを使用して、対応する reduce 関数を定義します。

    • valuesPricesは、map 関数によって出力され、keyCustIdでグループ化された price 値を要素とする配列です。

    • この関数は valuesPrice 配列をその要素の合計にまで減らします。

    var reduceFunction1 = function(keyCustId, valuesPrices) {
    return Array.sum(valuesPrices);
    };
  3. orders コレクション内のすべてのドキュメントに対して、map 関数 mapFunction1 と reduce 関数reduceFunction1 を使用して map-reduce を実行します。

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

    この操作は、結果をmap_reduce_exampleという名前のコレクションに出力します。map_reduce_exampleコレクションがすでに存在する場合、この操作により、内容が map-reduce 操作の結果に置き換えられます。

  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 }

次のとおり利用可能な集約パイプライン演算子を使用すると、カスタム関数を定義しなくても map-reduce 操作を書き換えることができます。

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 }

Tip

以下も参照してください。

カスタム集計式を使用する代替方法については、「Map-Reduceから集約パイプラインへの変換例」を参照してください。

次の例では、orders 上でord_date の値が 2020-03-01 以上となるすべてのドキュメントについての map-reduce 操作を示します。

この例での操作は次のとおりです。

  1. item.skuフィールドでグループ化し、各 sku の注文数と合計注文数量を計算します。

  2. sku値について、1 回の注文あたりの平均数量を計算し、結果を出力コレクションにマージします。

結果をマージするときに、既存のドキュメントが新しい結果と同じキーを持っている場合、操作によって既存のドキュメントが上書きされます。同じキーを持つ既存のドキュメントが存在しない場合は、この操作によってドキュメントが挿入されます。

手順の例:

  1. 各入力ドキュメントを処理する map 関数を定義します。

    • この関数では、 this は map-reduce 操作で処理中のドキュメントを参照します。

    • 各アイテムについて、この関数では skucount1 の新規オブジェクト value 、および注文アイテムの qty とと関連付け、skukeyに格納)と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. 2つの引数keySKUcountObjValsを使用して、対応する reduce 関数を定義します。

    • countObjVals は、map 関数によって reducer 関数に渡される、グループ化された keySKU 値にマップされたオブジェクトを要素とする配列です。

    • この関数は countObjVals 配列を count フィールドと qty フィールドを含む 1 つのオブジェクト 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. 2 つの引数keyreducedValを持つ finalize 関数を定義します。この関数は、 reducedValオブジェクトを変更してavgという名前の計算フィールドを追加し、変更されたオブジェクトを返します。

    var finalizeFunction2 = function (key, reducedVal) {
    reducedVal.avg = reducedVal.qty/reducedVal.count;
    return reducedVal;
    };
  4. orders コレクションに対して map-reduce 操作を実行するには、mapFunction2reduceFunction2、および finalizeFunction2 関数を使用します。

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

    この操作では、 query フィールドを使用して、 ord_date の値が new Date("2020-03-01")以上のドキュメントのみを選択します。次に、結果をコレクション map_reduce_example2に出力します。

    map_reduce_example2コレクションがすでに存在する場合、この操作により、内容が map-reduce 操作の結果に置き換えられます。つまり既存のドキュメントが新しい結果と同じキーを持っている場合、操作によって既存のドキュメントが上書きされます。同じキーを持つ既存のドキュメントが存在しない場合は、この操作によってドキュメントが挿入されます。

  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 } }

次のとおり利用可能な集約パイプライン演算子を使用すると、カスタム関数を定義しなくても map-reduce 操作を書き換えることができます。

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ステージでは各 SKU について計算し、items.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ステージでは、出力ドキュメントを再形成して、map-reduce の出力をミラーリングし、2 つのフィールド_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ステージでは各 SKU について計算し、items.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ステージでは、出力ドキュメントを再形成して、map-reduce の出力をミラーリングし、2 つのフィールド_idvalueを含めます。$projectセット:

    • $size を使用して、 value.countorders_ids配列のサイズに合わせます

    • 入力ドキュメントの qty フィールドについてのvalue.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 } }

Tip

以下も参照してください。

カスタム集計式を使用する代替方法については、「Map-Reduceから集約パイプラインへの変換例」を参照してください。

戻る

同時実行性