Nota
Canalización de agregación como alternativa a Map-Reduce
An La canalización de agregación proporciona un mejor rendimiento y facilidad de uso que una operación de mapa-reducción.
Las operaciones de mapa-reducción se pueden reescribir utilizando operadores de canalización de agregación, como $group, y$merge otros.
Para operaciones de map-reduce que requieren funcionalidad personalizada, MongoDB proporciona los $accumulator operadores de agregación y. Úselos para definir expresiones de agregación personalizadas en JavaScript.$function
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 canalización de agregación sin expresiones de agregación personalizadas. Para alternativas que utilizan expresiones personalizadas, consulte Ejemplos de traducción de Map-Reduce a canalización 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:
Define la función de mapa para procesar cada documento de entrada:
En la función,
thishace referencia al documento que la operación 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:valuesPriceses una matriz cuyos elementos son los valorespriceemitidos por la función de mapa y agrupados porkeyCustId.La función reduce la matriz
valuesPricea la suma de sus elementos.
var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; Realice map-reduce en todos los documentos de la colección
ordersutilizando la función mapmapFunction1y la función reducereduceFunction1: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.Consulta 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
Al utilizar los operadores de canalización de agregación disponibles, puede reescribir la operación 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 envía los siguientes documentos a la siguiente etapa:
{ "_id" : "Don Quis", "value" : 155 } { "_id" : "Ant O. Knee", "value" : 95 } { "_id" : "Cam Elot", "value" : 60 } { "_id" : "Busby Bee", "value" : 125 } Luego, escribe la salida en
$outlaagg_alternative_1colección. Como alternativa, podría usar en$mergelugar$outde.Consulta 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 obtener una alternativa que utilice expresiones de agregación personalizadas, consulte Ejemplos de traducción de Map-Reduce a canalización de agregación.
Calcular el pedido y la cantidad total con la cantidad promedio por artículo
En el siguiente ejemplo, verá una operación de mapa-reducción en la colección orders para todos los documentos que tengan un valor ord_date mayor o igual a 2020-03-01.
La operación en el ejemplo:
Agrupa por el campo
item.skuy calcula la cantidad de pedidos y la cantidad total solicitada para cadasku.Calcula la cantidad promedio por pedido para cada valor
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.
Pasos de ejemplo:
Define la función de mapa para procesar cada documento de entrada:
En la función,
thishace referencia al documento que la operación map-reduce está procesando.Para cada artículo, la función asocia el
skucon un nuevo objetovalueque contiene elcountde1y el artículoqtypara el pedido 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 una matriz cuyos elementos son los objetos asignados a los valores agrupadoskeySKUpasados por la función de mapa a la función reductora.La función reduce la matriz
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 lo sobrescribe. Si no existe ningún documento con la misma clave, la operación lo inserta.Consulta 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
Al utilizar los operadores de canalización de agregación disponibles, puede reescribir la operación 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 selecciona solo aquellos documentos
$matchconord_datemayor o igualnew Date("2020-03-01")a.La etapa desglosa el documento por
$unwindelitemscampo de la matriz para generar un documento por cada elemento de la matriz. 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
$groupitems.skuse agrupa por, calculando para cada SKU:- El campo
qty. El campoqtycontiene el - total
qtypedidos por cadaitems.sku$sum(Ver).
- El campo
- La matriz
orders_ids. El campoorders_idscontiene un - matriz de órdenes distintos
_idpara elitems.sku$addToSet(ver).
- La matriz
{ "_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 desglosa el documento por
$unwindelitemscampo de la matriz para generar un documento por cada elemento de la matriz. 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
$groupitems.skuse agrupa por, calculando para cada SKU:El
qtycampo. Elqtycampo contiene el total deqtypedidos por cadaitems.sku$sumutilizando.La
orders_idsmatriz. Elorders_idscampo contiene una matriz_iditems.sku$addToSetde de orden distintopara el que utiliza.
{ "_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 de laorders_idsmatriz$sizeutilizando.el campo
value.qtyalqtydel documento de entrada.el
value.avgal número promedio de cantidades por pedido utilizando$divide$sizey.
{ "_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, escribe la salida en
$mergelaagg_alternative_3colección. 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.Consulta 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 obtener una alternativa que utilice expresiones de agregación personalizadas, consulte Ejemplos de traducción de Map-Reduce a canalización de agregación.