Start wide, expand further, and never look back.
Arnold Schwarzenegger
En nuestro post anterior, aprendimos a realizar las operaciones básicas CRUD sobre una base de datos MongoDB, incluyendo consultas básicas de igualdad, desigualdad numérica (mayor y menor que) y conjunción y disyunción (or y and). Sin embargo, estos criterios de búsqueda rara vez serán suficiente para satisfacer nuestros requerimientos, por lo que en este post nos dedicaremos a explorar los otros operadores y criterios existentes en MongoDB para el comando find().
Antes que nada procedemos a conectarnos a una base de datos y a ingresar algunos datos de prueba. Para nuestro ejemplo, ingresamos los siguientes comandos en nuestra terminal:
use estudiantes
db.estudiantes.insertOne({nombre : 'Juan', apellido : 'Linarez', edad : 21, notas : [18,19,16], programa : { nombre : 'Ingeniería en Informática', nivel : 'pregrado'}, grupos: [ { nombre : 'Club de Ajedrez', descripción : 'Grupo de jugadores de Ajedrez'}, { nombre : 'Club de Informática', descripcion : 'Grupo para fanáticos de Tecnología'}] })
db.estudiantes.insertOne({nombre : 'Maria', apellido : 'Cadenas', edad : 18, notas : [12,14], programa : { nombre : 'Medicina', nivel : 'pregrado'}, grupos: [ { nombre : 'Club de Volleyball', descripción : 'Equipo d eVolleyball de la universidad.'}]})
db.estudiantes.insertOne({nombre : 'Jorge', apellido : 'Rodriguez', edad : 31, notas : [14,19,13], programa : { nombre : 'Ingeniería Civil', nivel : 'postgrado'}})
db.estudiantes.insertOne({nombre : 'Carla', apellido : 'Sandoval', edad : 24, notas : [15,17,16,15], programa : { nombre : 'Ingeniería en Informática', nivel : 'pregrado'}, grupos: [ { nombre : 'Club de Informática', descripcion : 'Grupo para fanáticos de Tecnología'}] })
db.estudiantes.insertOne({nombre : 'William', apellido : 'Smith', edad : 14, notas : ['A', 'B+', 'A-'], programa : { nombre : 'Bachiller'}, grupos: [ { nombre : 'Club de PS4', descripción : 'Grupo de jugadores de Playstation 4'}] })
NOTA: Como ya sabemos, podemos agregarlos todos utilizando insertMany(), pero para mantener la legibilidad lo dejaremos de esta forma.
Proyecciones:
NOTA: Solo podemos o mostrar n cantidad de elementos, o no mostrar n cantidad de elementos, no podemos hacer una mezcla de elementos a mostrar y ocultar, es decir, no podemos mezclar algunos campos con 1 y otros con 0. La única excepción a esta regla es el campo _id, el cual como siempre se muestra por defecto, podemos agregarlo a una proyección con campos a mostrar para que no lo muestre.
Por ejemplo (y aquí aprovecharemos para repasar los comandos básicos del post anterior):
Si queremos obtener solamente el nombre de aquellos estudiantes que sean mayores a 20 y menores a 30 años ejecutaríamos el siguiente comando:
db.estudiantes.find({edad: { $gt : 20, $lt : 30}},{nombre:1})
Ahora, si queremos obtener toda la información a excepción del programa y los grupos de todos los estudiantes que sean menores a 20 o mayores a 30 años, usaríamos el siguiente:
db.estudiantes.find({ $or : [{edad : {$gt : 30}},{edad : {$lt : 20}}]},{programa:0, grupos:0})
Operadores:
$gte y $lte son similares a $gt y $lt pero el criterio de igualdad también los satisface a diferencia de los otros. Sería decir que son “mayor o igual que…” y “menor o igual que…”
$in: nos permite verificar si el valor del campo buscar se encuentra dentro de un arreglo específico.
Ej: Si queremos obtener los documentos donde el nombre sea o “María”, o “José”, “Juan” o 10. Lo haríamos de la siguiente forma:
db.estudiantes.find({ nombre : { $in : ['Maria','José','Juan', 10] } },{nombre: 1, _id: 0})
NOTA: Recordar que como no existe un esquema en la base de datos, el tipo de dato de un campo específico no tiene que ser el mismo en todos los documentos, pueden haber documentos con notas numéricas y otras con notas alfabéticas como se tiene en el ejemplo.
$all: nos permite obtener todos los documentos que contengan un arreglo específico en el campo especificado.
Ej: Si queremos obtener todos los estudiantes con las notas 18, 19 y 16. Podemos utilizar el siguiente comando:
db.estudiantes.find({notas:[18,19,16]},{nombre:1,notas:1,_id:0})
$regex o /regex/: nos permite utilizar una expresión regular para ejecutar una búsqueda. Es con este comando que podemos hacer un equivalente al “LIKE” de SQL.
Ej: Si queremos obtener todos los estudiantes cuyo nombre empiece por la letra “J”, usemos el siguiente comando:
db.estudiantes.find({nombre:{$regex:/^J/}},{nombre:1,_id:0})
O si queremos obtener todos los estudiantes cuyo apellido contenga una “n”, usemos este otro:
db.estudiantes.find({apellido:/n/},{apellido:1,_id:0})
Búsqueda en documentos internos:
NOTA: En este caso debemos obligatoriamente pasar el campo como un string poniéndolo dentro de comillas.
Ej: Si quisiéramos obtener los estudiantes con programa de nivel “pregrado”, lo haríamos de la siguiente forma:
db.estudiantes.find({'programa.nivel' : 'pregrado'},{nombre:1,apellido:1,programa:1,_id:0})
Dentro de esta sentencia también podemos utilizar todos los demás operadores que ya vimos. Por ejemplo, si quisiéramos obtener los estudiantes con un programa cuyo nombre empiece por la letra “I”, y que además tenga nivel “pregrado”, podríamos utilizar el siguiente comando:
db.estudiantes.find({'programa.nivel' : 'pregrado', 'programa.nombre' : /^I/ },{nombre:1,apellido:1,programa:1,_id:0})
También podemos mezclar búsqueda en campos normales y documentos internos del mismo documento.
Ej: Si quisiéramos obtener los estudiantes mayores a 20 años con un programa de nivel pregrado lo haríamos de la siguiente forma:
db.estudiantes.find({'programa.nivel' : 'pregrado', edad : {$gt : 20} },{nombre:1,apellido:1,programa:1,_id:0, edad:1})
Búsqueda en arreglos internos
Por el contrario, si quisiéramos buscar los documentos en los que fuera suficiente que alguno de los elementos dentro de un arreglo interno cumpla con un criterio, podríamos hacerlo sencillamente utilizando el criterio de igualdad. Si el campo sobre el que estamos buscando es un arreglo, retornara los documentos en los que cualquiera de los elementos del arreglo cumpla el criterio.
Ej: Si quisiéramos encontrar los estudiantes que tienen en una de sus notas un 19, haríamos lo siguiente:
db.estudiantes.find({notas: 19},{nombre:1,apellido:1,notas:1,_id:0})
De la misma forma podemos utilizar los demás operadores que ya conocemos. Si quisiéramos obtener los estudiantes con al menos una nota mayor o igual a 17, usaríamos este comando:
db.estudiantes.find({notas: {$gte : 17}},{nombre:1,apellido:1,notas:1,_id:0})
O si quisiéramos utilizar una disyunción y obtener los estudiantes con alguna de sus notas mayor o igual a 12 y menor a 16 utilizaríamos este otro:
db.estudiantes.find({notas: {$gte : 12, $lt : 16}},{nombre:1,apellido:1,notas:1,_id:0})
$elemMatch: este operador lo utilizamos cuando queremos verificar que un criterio de búsqueda se aplique sobre todos los elementos de un arreglo, por lo que lo podemos utilizar para obtener el mismo resultado que el comando anterior de la siguiente forma:
db.estudiantes.find({notas: {$elemMatch : {$gte:12,$lt:16}}},{nombre:1,apellido:1,notas:1,_id:0})
Por otra parte, también podemos acceder y ejecutar criterios de búsqueda directamente sobre un elemento específico de un arreglo accediendo a él a través de su índice de la misma forma que lo haríamos con objetos incrustados.
Ej: si quisiéramos saber los estudiantes cuya primera nota es 12, haríamos lo siguiente:
db.estudiantes.find({'notas.0' : 12},{nombre:1,apellido:1,notas:1,_id:0})
NOTA: Como es un arreglo, el índice del primer elemento es el 0 y por lo tanto, el índice del n-ésimo elemento siempre sera n -1. Además, nótese como se pone el campo entre comillas para que sea tratado como un string al igual que si fuera un objeto incrustado.
Como último operador de arreglos, tenemos el $size, el cual nos permite obtener el tamaño de un arreglo para poder hacer búsquedas en base a este.
Ej: Si quisiéramos saber los estudiantes con 3 notas exclusivamente, lo haríamos de la siguiente forma:
db.estudiantes.find({ notas : {$size : 3}},{nombre:1,apellido:1,notas:1,_id:0})
Búsqueda en documentos incrustados en arreglos internos:
Si utilizamos el operador de igualdad, estaremos buscando un objeto incrustado en un arreglo que sea exactamente igual al que pasamos como parámetro.
Ej: El siguiente comando no me retorna los estudiantes que pertenecen al grupo “Club de Ajedrez”:
db.estudiantes.find({ grupos : {nombre:"Club de Ajedrez"}},{nombre:1,apellido:1,_id:0, grupos:1})
Para lograr ese objetivo debo pasar el objeto completo, es decir, también debo especificar la descripción de la siguiente forma:
db.estudiantes.find({ grupos : {nombre:"Club de Ajedrez", descripción: "Grupo de jugadores de Ajedrez"}},{nombre:1,apellido:1,_id:0, grupos:1})
Al igual que en los arreglos normales, podemos hacer búsquedas sobre un elemento específico utilizando su índice de la siguiente forma:
Ej: si quisiera obtener los estudiantes cuyo primer grupo fuera el de nombre “Club de Informática”, lo haría de la siguiente forma:
db.estudiantes.find({ 'grupos.0.nombre' : "Club de Informática"},{nombre:1,apellido:1,_id:0, grupos:1})
NOTA: Nuevamente no olvidar las comillas.
Ahora, si no queremos especificar el índice y queremos hacer una búsqueda general, lo hacemos sencillamente accediendo al campo directamente desde el nombre del arreglo sin pasar el índice.
Ej: si queremos obtener los estudiantes que pertenecen al grupo de nombre “Club de Informática”, hacemos lo siguiente:
db.estudiantes.find({ 'grupos.nombre' : "Club de Informática"},{nombre:1,apellido:1,_id:0, grupos:1})
De la misma forma, también podemos utilizar el operador $elemMatch para hacer búsquedas con múltiples criterios sobre alguno de los objetos incrustados.
Ej: Si queremos ver los estudiantes que pertenecen o al club de informática o al club de volleyball, hacemos lo siguiente:
db.estudiantes.find({ grupos : {$elemMatch : { $or : [{nombre:"Club de Informática"},{nombre: "Club de Volleyball"}] } } },{nombre:1,apellido:1,_id:0, grupos:1})
Finalmente, pongamos a prueba los conocimientos obtenidos haciendo una consulta más compleja.
Para obtener los estudiantes menores a 30 años, que tengan 3 notas registradas de las cuales al menos una sea mayor o igual a 18, estén en un programa de nivel “pregrado” y pertenezcan al curso de ajedrez o al de volleyball sin mostrar sus nombres pero si sus apellidos, haríamos lo siguiente:
db.estudiantes.find({ edad : { $lt : 30 }, notas : { $size : 3, $gte : 18 }, 'programa.nivel' : 'pregrado', grupos : { $elemMatch : { nombre : { $in : ['Club de Ajedrez', 'Club de Volleyball' ]}} } },{nombre:0,_id:0})
Exists:
Ej: Si quisiéramos saber los estudiantes que no pertenecen a ningún grupo, podríamos hacer lo siguiente:
db.estudiantes.find({grupos: {$exists : false}})
Existen otros operadores y comandos en MongoDB pero con los ya explicados creo que podemos satisfacer la tradicional regla de Paretto (80/20). Pero si quieres saber sobre algún otro comando no mencionado, o si tienes alguna duda sobre alguno de los que si hablamos, no dudes en comentar.
Publicado el 09 de mayo de 2019