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_id
の price
の合計値を計算します。
各入力ドキュメントを処理する map 関数を定義します。
この関数では、
this
は map-reduce 操作で処理中のドキュメントを参照します。この関数は、各ドキュメントの
price
をcust_id
にマッピングし、cust_id
とprice
を出力します。
var mapFunction1 = function() { emit(this.cust_id, this.price); }; 2つの引数
keyCustId
とvaluesPrices
を使用して、対応する reduce 関数を定義します。valuesPrices
は、map 関数によって出力され、keyCustId
でグループ化されたprice
値を要素とする配列です。この関数は
valuesPrice
配列をその要素の合計にまで減らします。
var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; orders
コレクション内のすべてのドキュメントに対して、map 関数mapFunction1
と reduce 関数reduceFunction1
を使用して map-reduce を実行します。db.orders.mapReduce( mapFunction1, reduceFunction1, { out: "map_reduce_example" } ) この操作は、結果を
map_reduce_example
という名前のコレクションに出力します。map_reduce_example
コレクションがすでに存在する場合、この操作により、内容が map-reduce 操作の結果に置き換えられます。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" } ])
$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 } 次に、
$out
により出力がコレクションagg_alternative_1
に書き込まれます。あるいは、$out
の代わりに$merge
を使用することもできます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 }
アイテムごとの平均数量で注文と合計数量を計算
次の例では、orders
上でord_date
の値が 2020-03-01
以上となるすべてのドキュメントについての map-reduce 操作を示します。
この例での操作は次のとおりです。
item.sku
フィールドでグループ化し、各sku
の注文数と合計注文数量を計算します。各
sku
値について、1 回の注文あたりの平均数量を計算し、結果を出力コレクションにマージします。
結果をマージするときに、既存のドキュメントが新しい結果と同じキーを持っている場合、操作によって既存のドキュメントが上書きされます。同じキーを持つ既存のドキュメントが存在しない場合は、この操作によってドキュメントが挿入されます。
手順の例:
各入力ドキュメントを処理する map 関数を定義します。
この関数では、
this
は map-reduce 操作で処理中のドキュメントを参照します。各アイテムについて、この関数では
sku
をcount
が1
の新規オブジェクトvalue
、および注文アイテムの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つの引数
keySKU
とcountObjVals
を使用して、対応する 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; }; 2 つの引数
key
とreducedVal
を持つ finalize 関数を定義します。この関数は、reducedVal
オブジェクトを変更してavg
という名前の計算フィールドを追加し、変更されたオブジェクトを返します。var finalizeFunction2 = function (key, reducedVal) { reducedVal.avg = reducedVal.qty/reducedVal.count; return reducedVal; }; orders
コレクションに対して map-reduce 操作を実行するには、mapFunction2
、reduceFunction2
、および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 操作の結果に置き換えられます。つまり既存のドキュメントが新しい結果と同じキーを持っている場合、操作によって既存のドキュメントが上書きされます。同じキーを持つ既存のドキュメントが存在しない場合は、この操作によってドキュメントが挿入されます。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" } } ] )
$match
ステージでは、ord_date
がnew Date("2020-03-01")
以上であるドキュメントのみが選択されます。$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" } ... $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 ] } $project
ステージでは、出力ドキュメントを再形成して、map-reduce の出力をミラーリングし、2 つのフィールド_id
とvalue
を含めます。$project
セット:$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" } ... $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 ] } $project
ステージでは、出力ドキュメントを再形成して、map-reduce の出力をミラーリングし、2 つのフィールド_id
とvalue
を含めます。$project
セット:$size
を使用して、value.count
をorders_ids
配列のサイズに合わせます入力ドキュメントの
qty
フィールドについてのvalue.qty
。
{ "_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 } } 最後に、
$merge
は出力をコレクションagg_alternative_3
に書き込みます。既存のドキュメントに新しい結果と同じキー_id
がある場合、この操作によって既存のドキュメントが上書きされます。 同じキーを持つ既存のドキュメントが存在しない場合は、この操作によってドキュメントが挿入されます。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 } }