Tutorial de MongoDB
PROFESOR/A
Jordi Conesa i Caralt
v6
Índice
Descripción del caso ........................................................................................................... 3
Familiarizándose con el entorno de trabajo ....................................................................... 5
Referencia de MongoDB .................................................................................................... 8
Inserción de documentos en colecciones ............................................................................ 8
Consulta de documentos ...................................................................................................11
Búsqueda en arrays ..........................................................................................................12
Búsqueda en subagregados ..............................................................................................13
Ordenación de resultados .................................................................................................14
Agregación .......................................................................................................................15
Creación de índices ...........................................................................................................17
Modificación de documentos ............................................................................................19
Algunos ejercicios más ......................................................................................................22
Como borrar una base de datos ........................................................................................24
2
v6
Descripción del caso
Una pequeña empresa de emprendedores ha concebido una aplicación para dispositivos
móviles que permita la compraventa de cualquier tipo de artículo entre particulares.
Para que un usuario pueda hacer uso de la aplicación debe registrase con un nombre de
usuario y una contraseña y aportar una dirección de correo electrónico y la dirección de un
punto de venta preferente donde entregar los artículos.
Para poner un ítem a la venta el vendedor debe indicar una breve descripción del artículo, el
precio de salida y la lista de etiquetas que lo describen. La aplicación incluirá también la fecha
en la que el artículo sale a la venta.
A fin de que los usuarios de la aplicación puedan buscar artículos que se vendan cerca de su
zona, la aplicación también guarda la longitud y latitud del punto de venta introducido por el
vendedor del objeto. La dirección definitiva de entrega puede variar, ya que comprador y
vendedor se pueden poner de acuerdo mediante correo electrónico para acordar el lugar que
mejor les venga a ambos.
Los compradores interesados en un determinado artículo pueden hacer contraofertas,
indicando su precio. La aplicación debe también guardar de cada contraoferta cuándo se
realizó y quién la realizó.
A efectos de visualización de resultados de las búsquedas, es importante reflejar el estado de
un artículo: disponible, vendido o descatalogado.
3
v6
La estructura del agregado usuario quedaría:
nombre
email
psw
dirección:
vía
número
ciudad
cp
El agregado ítem tendrá la siguiente estructura:
descripción
precio
fecha
estado
tags: [ ]
localización:
longitud
latitud
vendedor:
email
psw
ofertas: [
email
psw
oferta
fecha
4
v6
Familiarizándose con el entorno de trabajo
En la máquina virtual proporcionada encontraréis MongoDB instalado y ejecutándose. Cabe
destacar que el entorno de trabajo que ofrece por defecto MongoDB no es muy visual, por
tanto se ha instalado Robo3T ([Link] para tener un acceso más amigable a
MongoDB.
Encontraréis el icono para acceder a MongoDB en el escritorio.
Robo3T es un entorno gráfico que nos permite conectarnos a diferentes bases de datos
MongoDB. Por tanto, antes de empezar a trabajar con nuestra base de datos es necesario
crear una nueva conexión, indicando que la base de datos está en local (localhost) y el puerto
donde recibe las peticiones (20017 por defecto). En nuestro caso ya tenemos la conexión
creada por defecto (llamada “MongoDB en local”), por tanto, podemos conectarnos a la base
de datos usando directamente la conexión existente.
Una vez hecho nos aparecerá una pantalla como la siguiente, donde podemos ver que hay
información de sistema, una base de datos de configuración y una base de datos de películas
que utilizaremos para la actividad 2. Hasta entonces la dejamos aparcada.
5
v6
Dentro de MongoDB nos encontraremos las diferentes bases de datos en la parte izquierda
de la pantalla y podremos navegar por ellas. Además, al hacer click derecho con el ratón en
alguna de ellas (o en sus elementos) podremos ejecutar algunas operaciones. Una de las más
interesantes es la de abrir el interprete de comandos (open shell). Desde el interprete de
comandos podremos ejecutar sentencias en MongoDB sobre la base de datos seleccionada.
Podéis ver un ejemplo en la siguiente imagen, donde se observa la sentencia
[Link]() en el intérprete de comandos y el resultado de ejecutar dicha sentencia
debajo.
Para crear la base de datos que utilizaremos en el tutorial deberíais crear una base de datos
mediante la opción “Create database” accesible debajo del nombre de la conexión “MongoDB
en local”, tal y como podemos ver debajo.
6
v6
Cread una base de datos llamada “ventasParticulares” y una vez creada pulsad el botón derecho
encima y abrid el interprete de comandos (Open Shell) para empezar a trabajar con ella. Nos
aparecerá una nueva pestaña, con el nombre de la base de datos (ventasParticulares), donde
podemos empezar a entrar nuestros comandos de MongoDB.
7
v6
Referencia de MongoDB
Os recomendamos que tengáis siempre a mano la documentación de MongoDB
([Link] y su ficha de referencia
([Link]
Inserción de documentos en colecciones
Existen dos formas de añadir documentos a una colección:
1. Crear una variable con el documento y posteriormente añadirla a la colección con la
función insert.
2. Insertar directamente el documento en la función insert.
Veamos cada una de ellas.
Primero guardaremos un documento JSON en una variable (item1), para posteriormente
insertarla mediante la sentencia insert.
item1={
descripcion: "Mando xBox negro",
fecha: new Date ("2015, 3, 21"),
precio: 10,
tags: ["consolas", "xbox", "entretenimiento"],
vendedor: {email: "pperez@[Link]", psw: "pperez"},
localizacion: {longitude: 37.743671, latitude: -2.552276},
estado: "disponible",
contraofertas: [
{
email: "llopez@[Link]",
psw: "llopez",
oferta: 8,
fecha: new Date("2015, 4, 2")},
{
email: "ggomez@[Link]",
psw: "ggomez",
oferta: 7,
fecha: new Date("2015, 4, 13")}
]}
[Link](item1)
8
v6
También podríamos insertar el documento añadiéndolo como parámetro de la sentencia
insert, como podemos ver en los siguientes ejemplos:
[Link]({
descripcion: "Thermomix",
fecha: new Date ("2015, 5, 2"),
precio: 80,
tags: ["robot cocina", "menaje"],
vendedor: {email: "ffernandez@[Link]", psw: "ffernandez"},
localizacion: {longitude: 38.743671, latitude: -10.552276},
estado: "disponible"
})
[Link]({
descripcion: "Batidora",
fecha: new Date ("2018, 5, 14"),
precio: 10,
tags: ["cocina", "menaje"],
vendedor: {email: "ffernandez@[Link]", psw: "ffernandez"},
localizacion: {longitude: 38.743671, latitude: -10.552276},
estado: "disponible"
})
A continuación, vemos como podemos insertar múltiples documentos JSON pasándolos como
un array a la sentencia insert:
usr1={
nombre:"Luis López",
email: "llopez@[Link]",
psw:"llopez",
direccion: {
via:"C/Pez", num:3, ciudad:"Madrid", cp: "28031"}},
usr2={
nombre:"Francisco Fernández",
email: "ffernandez@[Link]",
psw:"ffernandez",
direccion: {
via:"C/Luna Nueva", num:145,
ciudad: "Barcelona", cp: "08009"}},
usr3={
nombre:"Gema Gomez",
email: "ggomez@[Link]",
psw:"ggomez",
direccion: {
via:"C/Sansa", num:28,
ciudad:"Valencia", cp: "46015"}},
usr4={
nombre:"Pepe Perez",
email: "pperez@[Link]",
9
v6
psw:"pperez",
direccion: {
via:"C/Sansa", num:79,
ciudad:"Valencia", cp: "46015"}},
usr5={
nombre:"Ana Alonso",
email: "aalonso@[Link]",
psw:"aalonso",
direccion: {
via:"C/Luna Llena", num:32,
ciudad:"Barcelona", cp: "08009"}}
[Link]([usr1, usr2, usr3, usr4, usr5])
10
v6
Consulta de documentos
Para mostrar todos los documentos de una colección se puede utilizar la función find (ver
[Link] para más
información).
[Link]()
La función find tiene dos argumentos el primero (query) sirve para indiciar los filtros que nos
permitirán seleccionar los documentos de interés. El segundo (proyection) permitirá indicar
qué elementos debe devolver la consulta.
En la proyección de los documentos resultantes de la búsqueda se muestra siempre y por
defecto el campo que contiene la clave (_id). Para no visualizar dicho campo debemos
especificarlo explícitamente poniéndolo a 0 en la proyección. Podemos indicar los campos
concretos que queremos que aparezcan indicando su nombre en la proyección seguido de :1.
Para entender mejor la función find os animamos a ejecutar las siguientes consultas y analizar
sus resultados:
[Link]({descripcion: "Thermomix"})
[Link]({},{precio:1, descripcion:1})
[Link]({descripcion: "Thermomix"},{_id:0, precio:1})
Como habéis podido observar, la primera consulta nos devuelve todos los documentos que
tienen un atributo descripcion con valor Thermomix, la segunda nos devuelve los atributos _id,
precio y descripción de todos los productos de la colección ítems, y la tercera el valor del
atributo precio de los documentos que tienen un atributo descripcion con valor Thermomix.
11
v6
Búsqueda en arrays
A continuación, vamos a ver cómo buscar dentro de atributos que contengan arrays en
MongoDB. Para ello utilizaremos el campo tags de la colección ítems, que contiene un array
con las palabras que definen al producto que se está vendiendo. Para más información, podéis
ejecutar la siguiente consulta:
[Link]({},{_id:0, descripcion:1, tags:1})
Si queremos mostrar la descripción de todos aquellos artículos etiquetados como consolas,
simplemente debemos indicar que el valor de tags debe ser consolas, como se muestra a
continuación.
[Link]( {tags: "consolas"},{_id:0, descripcion:1})
Si quisiéramos saber si hay algún producto etiquetado con los tags consolas y wii, deberíamos
añadir dos condiciones, una para cada etiqueta:
[Link](
{tags: "consolas", tags: "wii"},
{_id:0, descripcion:1})
Obviamente, no tendremos resultados ya que no tenemos ningún producto de Wii.
Por otro lado, si quisiéramos saber si hay algún producto etiquetado con los tags consolas o
menaje, deberíamos utilizar un operador, en este caso el operador in que nos devuelve cierto
si el array tags tiene algún valor de los de la lista suministrada:
[Link](
{tags: {$in:["consolas", "menaje"]}},
{_id:0, descripcion:1})
Podéis encontrar más información sobre el operador in y otros operadores permitidos en
[Link]
12
v6
Búsqueda en subagregados
Para poder filtrar por los valores de los documentos incrustados (o subagregados), se puede
acceder a sus atributos a partir de la composición del nombre del documento incrustado y el
nombre del atributo a consultar, con un punto en medio. Por ejemplo, la siguiente sentencia
nos devuelve los documentos con una oferta superior a 5 euros.
[Link](
{"[Link]": {$gt: 5}})
Otra consulta que podríamos realizar, y así introducimos las fechas, es obtener todos los
productos con una contraoferta anterior al febrero del 2019.
var feb2019 = new Date(2019, 2, 1)
[Link](
{"[Link]": {$lt: feb2019}} )
13
v6
Ordenación de resultados
Los resultados de una búsqueda se pueden ordenar utilizando la función sort. Está función se
concatena a la función find utilizada para obtener los resultados. Por ejemplo, vamos a obtener
todos los usuarios ordenados por su ciudad de origen:
[Link]().sort( { "[Link]": 1 } )
Evidentemente, si añadimos condiciones dentro del find, podemos elegir qué documentos
ordenar. Por ejemplo, vamos a ordenar los usuarios que viven en la provincia de Barcelona
(código postal = 08XXX) por ciudad. Para ello utilizaremos la función regex para seleccionar
aquellos códigos postales que empiecen por 08. Esa expresión regular se definiría en
MongoDB de la siguiente forma /^08.*/, ya que la barra inclinada “/x/” indica que x es una
expresión regular, la tilde “^” que debe empezar por los caracteres que siguen, que son 08.
Por tanto, devolvería todos los usuarios que tienen un código postal empezado por 08. La
consulta quedaría como sigue:
[Link]({
"[Link]": {$regex: /^08.*/}}
).sort( { "[Link]": 1 } )
14
v6
Agregación
En MongoDB podemos realizar agregaciones simples mediante funciones de agregación
como distinct, count, etc.
Por ejemplo, podemos mostrar los emails de los usuarios que tienen algún artículo a la venta,
eliminando repetidos añadiendo el distintct al final de la consulta:
[Link]("[Link]")
Otra función de agregado que Podemos utilizar es la función count, que nos permite contar,
por ejemplo, los usuarios de Madrid.
[Link]({"[Link]": "Madrid"})
Vemos que la consulta es equivalente a esta otra:
[Link]({"[Link]": "Madrid"}).count()
Aparte de estas operaciones simples, MongoDB proporciona un framework de agregación
muy potente que permite realizar cálculos complejos y nuevas agrupaciones de datos. Podéis
encontrar más información de dicho framework en
[Link] Aunque no es el objetivo de
este curso aprender como funciona el framework de agregación de MongoDB,
proporcionaremos algún ejemplo para que se aprenda a hacer agregaciones simples.
15
v6
Las agregaciones en MongoDB se realizan mediante la función aggregate. Una agregación
tiene diferentes fases, entre ellas destacamos la fase match i la fase group. La fase match
permite seleccionar los documentos a agregar y la fase group permite indicar como debemos
agrupar sus datos y calcular su resultado. A continuación, podemos ver un ejemplo de
aplicación sacado de la documentación de MongoDB1.
Sabiendo esto, imaginemos que queremos saber el nombre de los productos antiguos que
tiene a la venta cada usuario. Entendemos que un producto es antiguo si se puso a la venta
antes del 2020.
Vamos a ver como lo solucionamos. Primero debemos identificar sobre qué colección vamos
a ejecutar la agregación: como los productos están en la colección items trabajaríamos con
esa colección. Por otro lado, no queremos todos los productos, sino los antiguos, por tanto,
tendremos que añadir una cláusula match que seleccione los productos de antes del 2020.
Por último, queremos crear un array para cada usuario con el nombre de sus productos. Pues
por cada elemento que agreguemos, deberemos tomar su descripción y añadirla en un array.
Añadir un valor en un array se puede realizar mediante el operador push en MongoDB.
Podemos ver una posible solución a continuación:
fech2020 = new Date("2020, 1, 1")
[Link]([
{$match: { fecha: {$lt: fech2020 } } },
{$group: {
_id:"$[Link]",
articulos: {$push: "$descripcion"}}}])
1
[Link]
16
v6
Creación de índices
Por defecto, MongoDB crea un índice único sobre el atributo _id de cada colección, evitando
así la inserción de documentos con identificadores duplicados en la misma. Los otros campos
de los documentos pueden indexarse para mejorar el rendimiento de la base de datos. A
continuación, vamos a ver algunos ejemplos, para hacernos una idea de cómo se crean y de
qué permiten.
Podemos crear un índice sobre el campo de una colección; por ejemplo, indexar los ítems por
descripción:
[Link]({"descripcion":1})
Podemos crear índices también sobre campos de subagregados. Por ejemplo, podríamos
crear un índice sobre el campo email del agregado vendedor de la colección ítems:
[Link]({"[Link]":1})
Otro tipo de índices muy común son los índices compuestos, que indexan más de un campo,
cuando estos se consultan a menudo conjuntamente. En el siguiente ejemplo vemos como
crear un índice compuesto formado por los campos descripción y estado de la colección ítems:
[Link]({"descripcion":1,"estado":1})
MongoDB también permite indexar datos geográficos2. A continuación, veremos un ejemplo
donde se indexa la localización geográfica de los ítems. Crear índices geográficos permite la
inserción de operaciones geográficas dentro de nuestras consultas en MongoDB. Vamos a
ver un ejemplo donde primero creamos un índice geográfico:
[Link]({"localizacion":"2dsphere"})
2
Aprender a gestionar datos geográficos en MongoDB excede las expectativas del curso. Las consultas
presentadas se han añadido para ejemplificar el tema y mostrar las posibilidades de MongoDB.
17
v6
Una vez creado este tipo de índice podríamos buscar por ejemplo los artículos de la colección
items en un radio de 1000 metros respecto a donde se encuentra el usuario, suponiendo que
se encuentra en las siguientes coordenadas ([ 37.743670, -2.552276]):
[Link](
{"localizacion":
{$near:
{$geometry:
{type:"Point",
coordinates:[ 37.743670, -2.552276]
}, $maxDistance:1000
}}})
Fijaos que podríamos combinar operadores geográficos con operadores comunes en las
búsquedas. Por ejemplo, la siguiente consulta busca productos de tipo entretenimiento a 1km
de distancia de la posición actual.
[Link](
{
"tags": "entretenimiento",
"localizacion":
{$near:
{$geometry:
{type:"Point",
coordinates:[ 37.743670, -2.552276]
}, $maxDistance:1000
}}})
Al igual que en las bases de datos relacionales, en MongoDB también tenemos la función
explain3 que nos permite conocer el plan de acceso a una consulta para analizar su
rendimiento y evaluar la necesidad de añadir índices u otras estructuras de datos. Para
consultar el plan de consulta de una operación en MongoDB hay que añadir explain al final de
esta, como podemos ver a continuación.
[Link]({
"tags": "entretenimiento",
"localizacion":
{$near:
{$geometry:
{type:"Point",
coordinates:[ 37.743670, -2.552276]
}, $maxDistance:1000
}}}).explain()
3
Queda fuera del ámbito de este curso analizar las consultas para mejorar su rendimiento. No obstante, si
alguien está interesado en el tema, podrá encontrar más información en
[Link]
18
v6
Modificación de documentos
Las formas más comunes de modificar documentos en MongoDB es con las funciones update,
updateOne y UpdateMany. Los dos últimos permiten modificar un solo documento o múltiples
documentos. Estas funciones tienen 3 bloques de parámetros: las condiciones que indican
qué documentos hay que modificar (query), las modificaciones que deben hacerse (update) y
los parámetros que deben tenerse en cuenta.
Imaginad que queremos modificar el estado de todos nuestros productos a vendido.
Podríamos hacerlo con la siguiente consulta:
[Link]({},{$set: {estado:"vendido"}})
Esta consulta no tiene ninguna restricción (primer apartado) y por lo tanto se debería ejecutar
sobre todos los ítems, actualizando el estado a vendido (se hace en el segundo bloque de
parámetros mediante el operador set). Pero si miramos los ítems de nuestra base de datos,
veremos que sólo ha actualizado uno ¿? ¿Qué ha pasado?
Pues que la función update por defecto modifica sólo 1 documento. Para que pueda modificar
más de un documento es importante añadir el parámetro multi: true en el tercer bloque de la
actualización. Así la siguiente consulta funcionaria correctamente:
[Link]({},{$set: {estado:"vendido"}}, {multi: true})
Ahora, volvemos a dejar los ítems en estado disponible (mediante la sentencia updateMany):
[Link]({},{$set: {estado:"disponible"}})
Vamos a ver como podemos modificar distintos campos e íncluso añadir un subdocumento.
Suponed que nos piden modificar el documento con descripción Mando xBox negro para que
pase a estar vendido al usuario con email llopez@[Link] y contraseña llopez:
[Link](
{descripcion: "Mando xBox negro"},
{$set:{
estado: "vendido",
comprador: {email: "llopez@[Link]",
psw: "llopez"}}
})
19
v6
En este caso no hemos hecho un multi:true porque ya sabíamos que sólo había uno, pero en
caso contrario deberíamos haberlo hecho.
A continuación nos piden actualizar la colección de usuarios para añadir, en caso de que no
exista, al usuario con los siguientes datos: email: "ffernandez@[Link]", psw:"ffernandez2",
via:"C/Escalona", num:8, ciudad: "Barcelona"
[Link](
{email: "ffernandez@[Link]", psw:"ffernandez2"},
{$set:{email: "ffernandez@[Link]",
psw:"ffernandez2",
direccion: {
via:"C/Escalona",
num:8, ciudad: "Barcelona"}}},
{upsert:true}
)
Suponed que han cambiado el nombre de la C/ escalona por C/ Ancha. ¿Cómo
actualizaríamos nuestra base de datos?
[Link](
{"[Link]":"C/Escalona"},
{$set: {"[Link]":"C/Ancha"}},
{multi:true}
)
Modificar la base de datos para indicar que el artículo Thermomix ha recibido una contraoferta
del usuario con email ggomez@[Link] y contraseña ggomez" por 73 euros:
[Link](
{descripcion:"Thermomix"},
{$addToSet: {
contraofertas: {email: "ggomez@[Link]",
psw: "ggomez",
oferta: 73,
fecha: new Date("2020, 5, 15")}}
})
Fijaos que en el caso anterior hemos utilizado la función addToSet para añadir una nueva
oferta al array de ofertas.
20
v6
Ahora modificad el documento del producto Thermomix para informar que se ha vendido al
usuario anterior:
[Link](
{descripcion: "Thermomix"},
{$inc: {precio: -7},
$set: {estado: "vendido",
comprador: {email: "ggomez@[Link]",
psw: "ggomez"}}
}
)
En el caso anterior, en vez de asignar el precio 73 al producto, se ha calculado el valor final a
partir del precio de venta original (restándole 7 euros), para poder mostrar cómo funciona el
uso del operador inc.
Se pide que eliminéis la etiqueta entretenimiento de todos los documentos de la colección
items, siempre que exista:
[Link](
{tags: "entretenimiento"},
{$pull: {tags: "entretenimiento"}},
{multi: true}
)
Fijaos que para eliminar una etiqueta del array de etiquetas se utiliza el operador pull.
21
v6
Algunos ejercicios más
Para practicar un poco lo que habéis aprendido hasta aquí se propone resolver las siguientes
consultas:
Seleccionar la descripción de los ítems de tipo consola o menaje con un precio mayor de 20.
[Link](
{
tags: {$in:["consolas", "menaje"]},
precio: {$gt: 20} },
{
_id:0, descripcion:1})
Actualizar la colección ítems para añadir una contraoferta a todos los productos con estado
disponible, etiquetados como consola y con un precio mayor que 9, realizada por el usuario
con email jpepito@[Link], password jpepito, oferta de 8 euros y con fecha 03/03/2020.
[Link](
{
"estado" : "disponible",
"tags" : "consolas",
"precio": {$gt:9}
},
{
$push: {"contraofertas" :{
"email" : "`jpepito@[Link]",
"psw" : "jpepito",
"oferta" : 8,
"fecha" : ISODate("2020-03-03") }
}
},
{
"multi" : true} );
Actualizar la colección “ítems” para rebajar en 5 euros todos los productos en estado
disponibles para la venta registrados antes del año 2020.
[Link](
{ estado: "disponible",
fecha: {$lt: new Date(2020, 1, 1)}},
{ $inc:{precio: -5}},
{ multi: true} );
22
v6
Recuperar la descripción y el precio de todos los productos con estado disponible, etiquetados
como robot de cocina o menaje, con un precio menor de 100 euros y con una contraoferta
superior a 40 euros.
[Link](
{
estado:"disponible",
tags:{$in:["Robot de cocina", "menaje"]},
precio:{$lt:100},
"[Link]": {$gt: 40}
},
{
descripcion:1,
precio:1
});
Eliminar todos los registros de entretenimiento cuyo vendedor tenga por email
ffernandez@[Link].
[Link]({
tags: "entretenimiento",
"[Link]": "pperez@[Link]" });
23
v6
Como borrar una base de datos
Para eliminar documentos de nuestra base de datos podemos utilizar la función remove (más
información en [Link]
Esta función sólo tiene dos bloques de parámetros, uno para indicar las condiciones que
deben cumplir los documentos a borrar y otra para indicar las opciones de eliminación. La
siguiente sentencia por ejemplo, eliminaría todos los documentos de la colección items.
[Link]({})
Para eliminar una colección, podemos utilizar la función drop como se muestra a continuación.
[Link]()
Finalmente, comentar que si el objetivo es eliminar una base de datos, puede hacerse
mediante la siguiente sentencia:
[Link]()
24
v6