Nota
Pipeline de agregación como alternativa a Map-Reduce
An El pipeline de agregación ofrece un mejor rendimiento y usabilidad que una operación map-reduce.
Las operaciones de map-reduce pueden reescribirse utilizando los operadores del pipeline de agregación, como $group, $merge y otros.
Para operaciones de map-reduce que requieren funcionalidad personalizada, MongoDB proporciona los $accumulator y los $function operadores de agregación. Utilice estos operadores para definir expresiones de agregación personalizadas en JavaScript.
Enmongosh, el métododb.collection.mapReduce()envuelve el comandomapReduce. Los siguientes ejemplos utilizan el métododb.collection.mapReduce().
Los ejemplos de esta sección incluyen alternativas de pipelines de agregación sin expresiones de agregación personalizadas. Para alternativas que usan expresiones personalizadas, consulta Ejemplos de traducción del antiguo map-reduce al pipeline de agregación.
Crea una colección de muestra orders con estos documentos:
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" } ])
Devolver el precio total por cliente
Realice la operación map-reduce en la colección orders para agrupar por cust_id y calcular la suma de price para cada cust_id:
Defina la función de mapeo para procesar cada documento de entrada:
En la función,
thisse refiere al documento que la operación de map-reduce está procesando.La función asigna
priceacust_idpara cada documento y emitecust_idyprice.
var mapFunction1 = function() { emit(this.cust_id, this.price); }; Define la función de reducción correspondiente con dos argumentos
keyCustIdyvaluesPrices:El
valuesPriceses un arreglo cuyos elementos son los valorespriceemitidos por la función map y agrupados porkeyCustId.La función reduce la matriz
valuesPricea la suma de sus elementos.
var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; Realiza map-reduce en todos los documentos de la colección
ordersutilizando la función de mapeomapFunction1y la función de reducciónreduceFunction1:db.orders.mapReduce( mapFunction1, reduceFunction1, { out: "map_reduce_example" } ) Esta operación envía los resultados a una colección llamada
map_reduce_example. Si la colecciónmap_reduce_exampleya existe, la operación reemplazará el contenido con los resultados de esta operación de map-reduce.Query la colección
map_reduce_examplepara verificar los resultados:db.map_reduce_example.find().sort( { _id: 1 } ) La operación devuelve estos documentos:
{ "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Busby Bee", "value" : 125 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Don Quis", "value" : 155 }
Alternativa de agregación
Utilizando los operadores de la pipeline de agregación disponibles, se puede reescribir la operación de map-reduce sin definir funciones personalizadas:
db.orders.aggregate([ { $group: { _id: "$cust_id", value: { $sum: "$price" } } }, { $out: "agg_alternative_1" } ])
La
$groupetapacust_idagrupa por y calcula elvaluecampo (véase también).$sumElvaluecampo contiene el totalpricede paracust_idcada.La etapa generó los siguientes documentos para la etapa siguiente:
{ "_id" : "Don Quis", "value" : 155 } { "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Busby Bee", "value" : 125 } A continuación, la
$outescribe la salida en la colecciónagg_alternative_1. Alternativamente, podrías usar$mergeen lugar de$out.Query la colección
agg_alternative_1para verificar los resultados:db.agg_alternative_1.find().sort( { _id: 1 } ) La operación devuelve los siguientes documentos:
{ "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Busby Bee", "value" : 125 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Don Quis", "value" : 155 }
Tip
Para una alternativa que utiliza expresiones de agregación personalizadas, consulte Ejemplos de traducción de map-reduce a la pipeline de agregación.
Calcular el pedido y la cantidad total con la cantidad promedio por artículo
En el siguiente ejemplo, se verá una operación de map-reduce en la colección orders para todos los documentos que tengan un valor de ord_date mayor o igual a 2020-03-01.
La operación en el ejemplo:
Agrupa por el campo
item.skuy calcula el número de órdenes y la cantidad total ordenada para cadasku.Calcula la cantidad promedio por pedido para cada valor de
skuy fusiona los resultados en la colección de salida.
Al fusionar resultados, si un documento existente tiene la misma clave que el nuevo resultado, la operación lo sobrescribe. Si no existe ningún documento con la misma clave, la operación lo inserta.
Ejemplo de pasos:
Defina la función de mapeo para procesar cada documento de entrada:
En la función,
thisse refiere al documento que la operación de map-reduce está procesando.Para cada elemento, la función asocia el
skucon un nuevo objetovalueque contiene elcountde1y el elementoqtypara la orden y emite elsku(almacenado en elkey) y elvalue.
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); } }; Define la función de reducción correspondiente con dos argumentos
keySKUycountObjVals:countObjValses un arreglo cuyos elementos son los objetos asignados a los valores agrupadoskeySKUpasados por la función map a la función reducer.La función reduce el arreglo
countObjValsa un solo objetoreducedValueque contiene los camposcountyqty.En
reducedVal, el campocountcontiene la suma de loscountcampos de los elementos individuales de la matriz, y el campoqtycontiene la suma de losqtycampos de los elementos individuales de la matriz.
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; }; Define una función de finalización con dos argumentos
keyyreducedVal. La función modifica el objetoreducedValpara añadir un campo calculado llamadoavgy devuelve el objeto modificado:var finalizeFunction2 = function (key, reducedVal) { reducedVal.avg = reducedVal.qty/reducedVal.count; return reducedVal; }; Realice la operación map-reduce en la colección
ordersutilizando las funcionesmapFunction2,reduceFunction2yfinalizeFunction2:db.orders.mapReduce( mapFunction2, reduceFunction2, { out: { merge: "map_reduce_example2" }, query: { ord_date: { $gte: new Date("2020-03-01") } }, finalize: finalizeFunction2 } ); Esta operación utiliza el campo
querypara seleccionar solo los documentos con un valorord_datemayor o igual anew Date("2020-03-01"). Luego, envía los resultados a una colecciónmap_reduce_example2.Si la colección
map_reduce_example2ya existe, la operación fusionará el contenido existente con los resultados de esta operación de map-reduce. Es decir, si un documento existente tiene la misma clave que el nuevo resultado, la operación sobrescribirá el documento existente. Si no existe un documento con la misma clave, la operación inserta el documento.Query la colección
map_reduce_example2para verificar los resultados:db.map_reduce_example2.find().sort( { _id: 1 } ) La operación devuelve estos documentos:
{ "_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 } }
Alternativa de agregación
Utilizando los operadores de la pipeline de agregación disponibles, se puede reescribir la operación de map-reduce sin definir funciones personalizadas:
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" } } ] )
La etapa
$matchselecciona únicamente aquellos documentos conord_datemayor o igual anew Date("2020-03-01").La etapa
$unwinddesglosa el documento por el campo de arregloitemspara producir un documento por cada elemento del mismo. Por ejemplo:{ "_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" } ... La etapa
$groupagrupa por elitems.sku, calculando para cada sku:- El campo
qty. El campoqtycontiene la - total de
qtypedidos por cadaitems.sku(consultar$sum).
- El campo
- El arreglo
orders_ids. El campoorders_idscontiene un - matriz de órdenes distintos
_idpara elitems.sku$addToSet(ver).
- El arreglo
{ "_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 ] } La etapa modifica el documento de salida para reflejar la salida de map-reduce y tener dos
$projectcampos:_idvaluey. La$projectetapa establece:La etapa
$unwinddesglosa el documento por el campo de arregloitemspara producir un documento por cada elemento del mismo. Por ejemplo:{ "_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" } ... La etapa
$groupagrupa por elitems.sku, calculando para cada sku:El
qtycampo. Elqtycampo contiene el total deqtypedidos por cadaitems.sku$sumutilizando.El arreglo
orders_ids. El campoorders_idscontiene un arreglo de diferentes órdenes_idpara elitems.skuusando$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 ] } La etapa modifica el documento de salida para reflejar la salida de map-reduce y tener dos
$projectcampos:_idvaluey. La$projectetapa establece:el
value.countal tamaño del arregloorders_idsutilizando$size.value.qtyal campoqtydel documento de entrada.el
value.avgal número promedio de unidades por pedido utilizando$dividey$size.
{ "_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 } } Finalmente, el
$mergeescribe la salida en la colecciónagg_alternative_3. Si un documento existente tiene la misma clave_idque el nuevo resultado, la operación sobrescribe el documento existente. Si no existe ningún documento con la misma clave, la operación inserta el documento.Query la colección
agg_alternative_3para verificar los resultados:db.agg_alternative_3.find().sort( { _id: 1 } ) La operación devuelve los siguientes documentos:
{ "_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
Para una alternativa que utiliza expresiones de agregación personalizadas, consulte Ejemplos de traducción de map-reduce a la pipeline de agregación.