Tema 4 - MongoDB - Aggregate MapReduce CP

Page 1


AGREGACIÓN & MAPREDUCE EN MongoDB Tema 4


Índice ü

Aggregation Framework: •

ü

ü

primera forma - operadores

Map-Reduce: •

segunda forma

ejercicios

Métodos de agregación simple: • tercera forma

Agregación en MongoDB

2


Aggregation Framework

Agregación en MongoDB


Aggregation Framework §

Las operaciones de agregación procesan registros de datos y devuelven resultados calculados.

§

Las operaciones de agregación agrupan valores de varios documentos y pueden realizar una variedad de operaciones en los datos agrupados para obtener un único resultado. MongoDB proporciona tres formas de realizar la agregación:

§

Aggregation pipeline

Single purpose aggregation methods

Map-Reduce

Agregación en MongoDB

5


Aggregation Pipeline: primera forma • •

El framework de agregación de MongoDB se basa en el concepto de canalización (pipelines) de procesamiento de datos. Los documentos entran en una tubería de etapas múltiples que transforma los documentos en un resultado agregado. db.orders.aggregate([ { $match: { status: "A" } }, { $group: { _id: "$cust_id", total: { $sum: "$amount" } } } ])

• • •

Primera etapa: la etapa $match filtra los documentos por el campo indicado y pasa a la siguiente etapa aquellos documentos que cumplan dicha condición (en este caso status = "A“). Segunda etapa: la etapa $group agrupa los documentos por el campo indicado (cust_id) para calcular la suma de la cantidad para cada cust_id único. Las etapas de canalización más básicas proporcionan filtros que funcionan como consultas y transformaciones de documentos que modifican la forma del documento de salida. Agregación en MongoDB

6


Aggregation Pipeline: primera forma §

§ §

Otras operaciones de pipeline proporcionan herramientas para agrupar y clasificar documentos por campo o campos específicos, así como herramientas para agregar el contenido de los arrays, incluidas los arrays de documentos. Los pipelines pueden usar operadores para tareas como calcular el promedio o concatenar una cadena. Los pipelines proporcionan una agregación de datos eficiente utilizando operaciones nativas dentro de MongoDB, y es el método preferido para la agregación de datos en esta base de datos.

§

El pipeline puede operar en una colección fragmentada.

§ §

El pipeline puede usar índices para mejorar su rendimiento durante algunas de sus etapas. Además, estos tienen una fase de optimización interna. Agregación en MongoDB

7


Aggregation Pipeline: operadores Inserta estos datos en tu base de datos para que pruebes los ejemplos siguientes.

§ $abs: valor absoluto

{ _id: 1, name: "juan", budget: 5, spent: 8, date: ISODate("2014-03-01T08:00:00Z") } { _id: 2, name: “ana“, budget : 4, spent : 4, date: ISODate("2014-03-05T09:00:00Z") } { _id: 3, name: "jorge“, budget : 9, spent : 7, date: ISODate("2014-04-01T09:00:00Z") } { _id: 4, name: “manuel“, budget : 6, spent : 7, date: ISODate("2014-05-01T09:30:00Z") }

{$abs: <number>} > db.budgets.aggregate([ { $project: { total: { $abs: { $subtract: [ "$budget", "$spent" ] } } } } ])

§ $add: sumar o fechas > db.budgets.aggregate([ { $project: { name: 1, total: { $add: [ "$budget", "$spent" ] } } } ]) > db.budgets.aggregate( [{ $project:{name: 1, billing_date: {$add: ["$date",3*24*60*60000 ]}}} ])


Aggregation Pipeline: operadores db.sales.insertMany([ { "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-01-01T08:00:00Z") }, Inserta estos datos en tu base de datos para que pruebes los ejemplos siguientes.

{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : ISODate("2014-02-03T09:00:00Z") }, { "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : ISODate("2014-02-03T09:05:00Z") }, { "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10, "date" : ISODate("2014-02-15T08:00:00Z") }, { "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-02-15T09:12:00Z") }])

$addToSet: devuelve un array de todos los valores únicos que resultan de aplicar una expresión a cada documento en un grupo de documentos que comparten el mismo grupo por clave. El orden de los elementos en la matriz de salida no está especificado. >

db.sales.aggrega te( [{ $group: { _id: { day: { $dayOfYear: "$date"}, year: { $year: "$date" } }, itemsSold: { $addToSet: "$item" } } }] ) { "_id" : { "day" : 46, { "_id" : { "day" : 34, { "_id" : { "day" : 1,

"year" : 2014 }, "itemsSold" : "year" : 2014 }, "itemsSold" : "year" : 2014 }, "itemsSold" :

[ "xyz", "abc" ] } [ "xyz", "jkl" ] } [ "abc" ] }


Aggregation Pipeline: operadores Inserta estos datos en tu base de datos para que pruebes los ejemplos siguientes.

db.new_students.insertMany([ { "_id": 1, "quizzes": [ 10, 6, 7 ], "labs": [ 5, 8 ], "final": 80, "midterm": 75 }, { "_id": 2, "quizzes": [ 9, 10 ], "labs": [ 8, 8 ], "final": 95, "midterm": 80 }, { "_id": 3, "quizzes": [ 4, 5, 5 ], "labs": [ 6, 5 ], "final": 78, "midterm": 70 }])

§ $and: evalúa una o más expresiones y devuelve verdadero si todas las expresiones son verdaderas o si se evocan sin expresiones de argumento. De lo contrario devuelve falso. > db.sales.aggregate( [{ $project: { item: 1, qty: 1, result:{ $and:[ { $gt:[ "$price", 10 ] }, {$lt:[ "$price", 20]}]} } }] )

§ $avg db.new_students.aggregate([ { $project:{ quizAvg: { $avg:"$quizzes"}, labAvg:{$avg: "$labs" }, examAvg: { $avg: [ "$final", "$midterm" ] } } } ])


Aggregation Pipeline: operadores Inserta estos datos en tu base de datos para que pruebes los ejemplos siguientes.

db.new_items.insertMany([ { _id: 1, value: 9.25 }, { _id: 2, value: 8.73 }, { _id: 3, value: 4.32 }, { _id: 4, value: -5.34 }])

§ $ceil: devuelve el entero más pequeño mayor o igual al número especificado. > db.new_items.aggregate( [ { $project: { value: 1, ceilingValue: { $ceil: "$value" } } } ] ) { "_id" : 1, "value" : 9.25, "ceilingValue" : 10 }

§ $cmp: compara dos valores y retorna: -1 si el primero es menor que el segundo, 0 si son iguales y 1 si el primero es mayor que el segundo. > db.sales.aggregate( [{ $project: { item: 1, price: 1, cmpTo10: { $cmp: [ "$quantity", 10 ] }, _id: 0 } }])


Aggregation Pipeline: operadores § $concat: devuelve el entero más pequeño mayor o igual al número especificado. > db.sales.aggregate( [{ $project: { salesDetail: { $concat: [ "$item", " - ", "$price" ] } } } ] )

§ $concatArrays > db.new_students.aggregate( [{ $project: { calificaciones: { $concatArrays: [ "$quizzes", "$labs" ] } } } ] )

§ $divide > db.budgets.aggregate( [{ $project: { name: 1, discount: { $divide: [ "$spent", 2 ] } } } ] )


Aggregation Pipeline: operadores Inserta estos datos en tu base de datos para que pruebes los ejemplos siguientes.

db.Warehouse.insertMany([ { "_id" : 1, instock: [ "chocolate" ], ordered: [ "butter", "apples" ] }, { "_id" : 2, instock: [ "apples", "pudding", "pie" ] }, { "_id" : 3, instock: [ "pears", "pecans"], ordered: [ "cherries" ] }, { "_id" : 4, instock: [ "ice cream" ], ordered: [ ] }])

§ $cond: dermite el uso de condiciones para realizar la agregación. > db.warehouses.aggregate([ { $project: { items: { $cond: { if: { $and: [ { $isArray: "$instock" }, { $isArray: "$ordered" } ] }, then: { $concatArrays: [ "$instock", "$ordered" ] }, else: "One or more fields is not an array." } } } } ])

§ $isArray: con base al ejemplo, ¿qué hace este operador?


Aggregation Pipeline: operadores § $push: devuelve una matriz de todos los valores que resultan de aplicar una expresión a cada documento en un grupo de documentos que comparten el mismo grupo por clave. >

db.sales.aggregate( [{ $group: { _id: { day: { $dayOfYear: "$date"}, year: { $year: "$date" } }, itemsSold: { $push: { item: "$item", quantity: "$quantity" } } } }]

) > db.products.update( { "category_ids": 12 }, { "$push": { "category_ids": 12 } } ) > db.products.update( { "category_ids": 12 }, { "$pull": { "category_ids": { "$gt": 20 } } } ) $push (+end) $pop (-end), $pull (-n), $pullAll (-*)


Aggregation Pipeline: operadores § $pop: -1/1: remueve el primero o último elemento de un array. > db.norders.update({ _id: 1}, {$pop : { items: -1 } }) > db.norders.update({ _id: 1}, {$pop : { items: 1 } })

§ $pull db.stores.update( { }, { $pull: { fruits: { $in: [ "apples", "oranges" ] }, vegetables: "carrots" } }, { multi: true } )

§ $push > db.students.update( { _id: 1 }, { $push:{scores:89}} )

$each > db.students.update( { name: "joe" }, { $push:{scores:{$each:[90,92,85]} }} )


Map-Reduce: segunda forma

Agregación en MongoDB


Map-Reduce: segunda forma • •

• • • • •

MongoDB también proporciona operaciones de map-reduce para realizar la agregación. Las operaciones de map-reduce tienen dos fases: – Una etapa de map que procesa cada documento y emite uno o más objetos para cada documento de entrada, y – Una fase de reduce que combina la salida de la operación del map. Opcionalmente, map-reduce puede tener una etapa de finalización para realizar modificaciones finales al resultado. Map-reduce puede especificar una condición de consulta para seleccionar los documentos de entrada, así como ordenar y limitar los resultados. Map-reduce utiliza funciones de JavaScript personalizadas para realizar el map y el reduce de las operaciones, así como la operación de finalización opcional. El uso de JavaScript proporciona una gran flexibilidad en comparación con los pipeline, pero map-reduce es menos eficiente y más complejo. Map-reduce puede operar en una colección fragmentada y su resultado puede ser una colección fragmentada. Agregación en MongoDB

17


Map-Reduce: segunda forma


Map-Reduce: segunda forma • • • •

A partir de MongoDB 2.4, ciertas funciones y propiedades de shell mongo son inaccesibles en las operaciones de reducción de mapas. MongoDB 2.4 también proporciona soporte para múltiples operaciones de JavaScript para ejecutarse al mismo tiempo. Antes de MongoDB 2.4, el código JavaScript se ejecutaba en un solo hilo, lo que generaba problemas de concurrencia para reducir el mapa. A partir de la versión 4.2, se depreca la opción de map-reduce para crear una nueva colección fragmentada, así como el uso de la opción de fragmentación para map-reduce. Para enviar a una colección fragmentada, cree primero la colección fragmentada. MongoDB 4.2 también depreca el reemplazo de una colección fragmentada existente y la especificación explícita de nonAtomic: false.

Agregación en MongoDB

19


Inserta estos datos en tu base de datos

Map-Reduce: segunda forma

db.norders.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" } ])


Map-Reduce: segunda forma Recuerda: • La función de map emite pares clave-valor. Para aquellas claves que tienen múltiples valores, MongoDB aplica la fase de reduce, que recopila y condensa los datos agregados. • MongoDB luego almacena los resultados en una colección. • Opcionalmente, la salida de la función de reduce puede pasar a través de una función de finalización para condensar o procesar aún más los resultados de la agregación. • Todas las funciones de map-reduce en MongoDB son JavaScript y se ejecutan dentro del proceso mongod.

Agregación en MongoDB

21


Map-Reduce: ejercicio 1 var mapFunction1 = function() { emit(this.cust_id, this.price); }; var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; db.norders.mapReduce( mapFunction1, reduceFunction1, { out:"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.norders.aggregate([ { $group: { _id: "$cust_id", value: { $sum: "$price" } } }, { $out: "agg_alternative_1" } ])


Map-Reduce: ejercicio 2 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); } }; reduceFunction2 = function(keySKU, countObjVals) { var 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; };

Esta es una nueva función

var finalizeFunction2 = function (key, reducedVal) { reducedVal.avg = reducedVal.qty/reducedVal.count; return reducedVal; };


Map-Reduce: ejercicio 2 db.norders.mapReduce( mapFunction2, reduceFunction2, { out: { merge: "map_reduce_example2" }, query: { ord_date: { $gte: new Date("2020-03-01") } }, finalize: finalizeFunction2 } ); db.map_reduce_example2.find().sort( { _id: 1 } )

Mirad la nueva función

Agregación en MongoDB

24


Map-Reduce: ejercicio 2 db.norders.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" } } ] ) db.agg_alternative_3.find().sort( { _id: 1 } )

Comparad los dos resultados: map_reduce_example2 y agg_alternative_3


Map-Reduce: ejercicio 2 (explicación) $match: ord_date >= new Date("2020-03-01") $unwinds: { "_id" : 1, "cust_id" : "Ant O. Knee", "ord_date" : ISODate("2020-0301T00: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-0301T00:00:00Z"), "price" : 25, "items" : { "sku" : "apples", "qty" : 5, "price" : 2.5 }, "status" : "A" } $group: { "_id" : { "_id" : { "_id" : { "_id" : { "_id" : $project: { "_id" : { "_id" : { "_id" : { "_id" : { "_id" :

"chocolates", "qty" : 15, "orders_ids" : [ "oranges", "qty" : 63, "orders_ids" : [ 4, "carrots", "qty" : 15, "orders_ids" : [ 6, "apples", "qty" : 35, "orders_ids" : [ 9, 8, "pears", "qty" : 10, "orders_ids" : [ 3 ] }

2, 5, 8 ] } 7, 3, 2, 9, 1, 10 9 ] } 1, 6 ] }

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

] }


Map-Reduce: ejercicios 3 y 4 db.usersessions.insertMany([ { userid: "a", start: ISODate('2020-03-03 14:17:00'), { userid: "b", start: ISODate('2020-03-03 14:23:00'), { userid: "c", start: ISODate('2020-03-03 15:02:00'), { userid: "d", start: ISODate('2020-03-03 16:45:00'), { userid: "a", start: ISODate('2020-03-04 11:05:00'), { userid: "b", start: ISODate('2020-03-04 13:14:00'), { userid: "c", start: ISODate('2020-03-04 17:00:00'), { userid: "d", start: ISODate('2020-03-04 15:37:00'), ])

length: length: length: length: length: length: length: length:

3 95 }, 110 }, 120 }, 45 }, 105 }, 120 }, 130 }, 65 }

4 db.usersessions.insertMany([ { userid: "a", ts: ISODate('2020-03-05 14:17:00'), length: 130 }, { userid: "b", ts: ISODate('2020-03-05 14:23:00'), length: 40 }, { userid: "c", ts: ISODate('2020-03-05 15:02:00'), length: 110 }, { userid: "d", ts: ISODate('2020-03-05 16:45:00'), length: 100 } ])


Map-Reduce: ejercicio 3 var mapFunction = function() { var key = this.userid; var value = { total_time: this.length, count: 1, avg_time: 0 }; emit( key, value ); }; var reduceFunction = function(key, values) { var reducedObject = { total_time: 0, count:0, avg_time:0 }; values.forEach(function(value) { reducedObject.total_time += value.total_time; reducedObject.count += value.count; }); return reducedObject; }; var finalizeFunction = function(key, reducedValue) { if (reducedValue.count > 0) reducedValue.avg_time = reducedValue.total_time / reducedValue.count; return reducedValue; };


Map-Reduce: ejercicio 3 3

db.usersessions.mapReduce( mapFunction, reduceFunction, { out: "session_stats", finalize: finalizeFunction } ) db.session_stats.find().sort( { _id: 1 } ) { "_id" : "a", "value" : { "total_time" : { "_id" : "b", "value" : { "total_time" : { "_id" : "c", "value" : { "total_time" : { "_id" : "d", "value" : { "total_time" :

200, "count" : 2, "avg_time" : 100 } } 230, "count" : 2, "avg_time" : 115 } } 250, "count" : 2, "avg_time" : 125 } } 110, "count" : 2, "avg_time" : 55

} }


Map-Reduce: ejercicio 4 4

db.usersessions.mapReduce( mapFunction, reduceFunction, { query: { ts: { $gte: ISODate('2020-03-05 00:00:00') } }, out: { reduce: "session_stats" }, finalize: finalizeFunction } ) db.session_stats.find().sort( { _id: 1 } ) { { { {

"_id" "_id" "_id" "_id"

: : : :

"a", "b", "c", "d",

"value" "value" "value" "value"

: : : :

{ { { {

"total_time" "total_time" "total_time" "total_time"

: : : :

330, 270, 360, 210,

Agregación en MongoDB

"count" "count" "count" "count"

: : : :

3, 3, 3, 3,

"avg_time" "avg_time" "avg_time" "avg_time"

: : : :

110 90 120 70

} } } }

} } } }

30


Map-Reduce: ejercicio 5 ¡Cuidado!

5

db.usersessions.drop(); db.usersessions.insertMany([ { userid: "a", start: ISODate('2020-0303 { userid: "b", start: ISODate('2020-0303 { userid: "c", start: ISODate('2020-0303 { userid: "d", start: ISODate('2020-0303 { userid: "a", start: ISODate('2020-0304 { userid: "b", start: ISODate('2020-0304 { userid: "c", start: ISODate('2020-0304 { userid: "d", start: ISODate('2020-03]) 04

14:17:00') , 14:23:00') , 15:02:00') , 16:45:00') , 11:05:00') , 13:14:00') , 17:00:00') , 15:37:00') ,

length: 95 }, length: 110 }, length: 120 }, length: 45 }, length: 105 } , length: 120 } , length: 130 } , length: 65 }


Map-Reduce: ejercicio 5 db.usersessions.aggregate([ { $group: {_id: "$userid", total_time: {$sum: "$length" }, count: {$sum: 1 }, avg_time: {$avg: "$length" }}}, { $project: { value: { total_time: "$total_time", count: "$count", avg_time: "$avg_time" } } }, { $merge: { into: "session_stats_agg", whenMatched: [ { $set: { "value.total_time": { $add: [ "$value.total_time", "$$new.value.total_time" ] }, "value.count": { $add: [ "$value.count", "$$new.value.count" ] }, "value.avg": {$divide: [ {$add: ["$value.total_time", "$$new.value.total_time" ] }, {$add: [ "$value.count", "$$new.value.count" ] } ] } } } ], whenNotMatched: "insert" }} ])

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


Single Aggregation Methods: tercera forma

Agregación en MongoDB


Métodos de agregación simple: tercera forma

§

§

MongoDB proporciona db.collection.estimatedDocumentCount(), db.collection.count() y db.collection.distinct(). Todas estas operaciones agregan documentos de una sola colección. Si bien estas operaciones proporcionan acceso simple a los procesos de agregación comunes, carecen de la flexibilidad y las capacidades de los pipelines o map-reduce. Agregación en MongoDB

33



Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.