Filtros de búsqueda en MongoDB

filtros de búsqueda en MongoDBEste es el tercer artículo correspondiente a las operaciones CRUD en MongoDB (Create, Read, Update, Delete). En el artículo anterior se mostró cómo buscar documentos en MongoDB de forma genérica y en este se va a profundizar en este tema especificando los distintos filtros de búsqueda en MongoDB.

Filtros de igualdad

Los filtros de igualdad se usan para buscar documentos que cumplan una condición de igualdad sobre una o varias claves en concreto. Para realizar este tipo de búsqueda se pasa como primer parámetro del método find las claves con su correspondiente valor.

De este modo, los documentos que contenga el valor especificado para cada clave serán devueltos como resultado de la búsqueda.
Por ejemplo, para buscar el estudiante número 5 ser haría:

> db.scores.find({ student: 5})
{ "_id" : ObjectId("513c8538cd61c58d37e332d5"), "student" : 5, "type" : "exam", "score" : 43 }
{ "_id" : ObjectId("513c8538cd61c58d37e332d6"), "student" : 5, "type" : "essay", "score" : 18 }
{ "_id" : ObjectId("513c8538cd61c58d37e332d7"), "student" : 5, "type" : "quiz", "score" : 1 }

o para buscar todos los documentos en cuyo contenido exista una puntuación de 100 en el examen:

> db.scores.find({ type: "exam", score: 100})
{ "_id" : ObjectId("513c8538cd61c58d37e332f6"), "student" : 16, "type" : "exam", "score" : 100 }
{ "_id" : ObjectId("513c8538cd61c58d37e3349d"), "student" : 157, "type" : "exam", "score" : 100 }
{ "_id" : ObjectId("513c8538cd61c58d37e335f0"), "student" : 270, "type" : "exam", "score" : 100 }
...

Filtros por rangos

Los filtros por rangos son de gran utilidad para buscar documentos con claves cuyo valor cumpla una condición de “mayor o menor que” o este comprendido entre un determinado rango.

Esto, como siempre, se entiende mucho mejor con unos ejemplos que explicando la sintaxis concreta de cada condicional. Se usará principalmente la clave “score” la cual contiene valores numéricos.

$lt: Buscar valores menores que

db.scores.find({ score: { $lt: 50 } })
{ "_id" : ObjectId("513c8538cd61c58d37e332c9"), "student" : 1, "type" : "exam", "score" : 20 }
{ "_id" : ObjectId("513c8538cd61c58d37e332ca"), "student" : 1, "type" : "essay", "score" : 38 }
{ "_id" : ObjectId("513c8538cd61c58d37e332cb"), "student" : 1, "type" : "quiz", "score" : 23 }
...

En este caso el valor indicado se excluye, es decir, se omiten los valores coincidentes con 50.

$lte: Buscar valores iguales o menores que un valor

> db.scores.find({ score: { $lte: 50 } })
{ "_id" : ObjectId("513c8538cd61c58d37e332c9"), "student" : 1, "type" : "exam", "score" : 20 }
{ "_id" : ObjectId("513c8538cd61c58d37e332ca"), "student" : 1, "type" : "essay", "score" : 38 }
{ "_id" : ObjectId("513c8538cd61c58d37e332cc"), "student" : 2, "type" : "exam", "score" : 50 }

Esto nos daría todos los alumnos suspensos.

$gt: Buscar valores mayores que un valor

> db.scores.find({ score: { $gt: 95 } })
{ "_id" : ObjectId("513c8538cd61c58d37e332cf"), "student" : 3, "type" : "exam", "score" : 99 }
{ "_id" : ObjectId("513c8538cd61c58d37e332f6"), "student" : 16, "type" : "exam", "score" : 100 }
{ "_id" : ObjectId("513c8538cd61c58d37e332f8"), "student" : 16, "type" : "quiz", "score" : 97 }
..

En este caso el valor indicado se excluye, es decir, se omiten los valores coincidentes con 95.

$gte: Buscar valores iguales o mayores que un valor

db.scores.find({ score: { $gte: 95 } })
{ "_id" : ObjectId("513c8538cd61c58d37e332cf"), "student" : 3, "type" : "exam", "score" : 99 }
{ "_id" : ObjectId("513c8538cd61c58d37e332f6"), "student" : 16, "type" : "exam", "score" : 100 }
{ "_id" : ObjectId("513c8538cd61c58d37e33345"), "student" : 42, "type" : "essay", "score" : 95 }
...

Rangos numéricos

Estos filtros se pueden complementar entre sí además de servir como añadido para criterios de igualdad. Como ejemplo, vamos a ver todos los exámenes con una puntuación entre 50 (incluido) y 60 (no incluido):

> db.scores.find({ score: { $lt: 60, $gte: 50 }, type: "exam" })
{ "_id" : ObjectId("513c8538cd61c58d37e332c6"), "student" : 0, "type" : "exam", "score" : 56 }
{ "_id" : ObjectId("513c8538cd61c58d37e332cc"), "student" : 2, "type" : "exam", "score" : 50 }
{ "_id" : ObjectId("513c8538cd61c58d37e332db"), "student" : 7, "type" : "exam", "score" : 57 }
...

Rangos de caracteres

Al igual que con valores numéricos, los operadores anteriores pueden ser usados con cadenas de caracteres para realizar comparaciones lexicográficas. Para esto es importante saber que la codificación por defecto de MongoDB es UTF-8 por lo que las comparaciones y ordenaciones de cadenas de caracteres se realizan basándose en el orden de los códigos UTF-8.

Antes de comenzar se van a insertar algunos documentos en una colección llamada “people”:

> db.people.insert({ name: "Jose David" })
> db.people.insert({ name: "Alice" })
> db.people.insert({ name: "Bob" })
> db.people.insert({ name: "Charlie" })
> db.people.insert({ name: "Dave" })
> db.people.insert({ name: "Fred" })
> db.people.insert({ name: "George" })
> db.people.insert({ name: "Will" })

Veamos algunos ejemplos buscando valores menores que “D” y mayores que “B”:

> db.people.find({ name: { $lt: "D"} })
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
> db.people.find({ name: { $gt: "B"} })
{ "_id" : ObjectId("513cb9b3cd61c58d37e33e7e"), "name" : "Jose David" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cb9d3cd61c58d37e33e83"), "name" : "Fred" }
{ "_id" : ObjectId("513cb9decd61c58d37e33e84"), "name" : "George" }
{ "_id" : ObjectId("513cb9e2cd61c58d37e33e85"), "name" : "Will" }

Como se puede observar, “Bob”, es una cadena mayor que “B” lexicográficamente simplemente por que es más larga (“Bob” es mayor que “B”).
Si insertamos un valor de clave “name” numérico por ejemplo:

> db.people.insert({ name: 89 })

y ejecutamos las sentencias anteriores veremos que no aparece. Esto se debe a que sólo esta comparando cadenas de caracteres e ignora los valores numéricos ya que es el tipo del valor que se ha puesto en la comparación. Para comparar de este modo claves que puedan ser cadenas de caracteres y valores numéricos habrá que hacer consultas más complejas

Del mismo modo que con valores numéricos también se puede filtrar por rangos:

> db.people.find({ name: { $gt: "C"}, name: {$lt: "D"} })
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
> db.people.find({ name: { $lt: "D", $gt: "C"} })
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }

Bien, ahora se va ver que pasaría si se realiza la consulta anteriores especificando los rangos para la misma clave como parámetros separados:

> db.people.find({ name: { $lt: "D"}, name: {$gt: "C"} })
{ "_id" : ObjectId("513cb9b3cd61c58d37e33e7e"), "name" : "Jose David" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cb9d3cd61c58d37e33e83"), "name" : "Fred" }
{ "_id" : ObjectId("513cb9decd61c58d37e33e84"), "name" : "George" }
{ "_id" : ObjectId("513cb9e2cd61c58d37e33e85"), "name" : "Will" }

Se puede ver como ignora el primer parámetro y muestra los documentos que cumplen sólo la condición del segundo. Esto es debido a que al analizar MongoDB los condicionales el segundo sobrescribe al primero, por eso, si se desean especificar varios condicionantes sobre un mismo campo clave se debe hacer en el mismo parámetro.

Personalizando las consultas

En MongoDB los documentos de una misma colección no tienen por qué tener la misma estructura, de este modo se pueden consultar los documentos que poseen un determinado campo o, por el contrario, los que no lo poseen.

Para realizar esto se usa el operador $exists.  A continuación algunos ejemplos.

Primero, vamos a añadir a la colección “people” algunos documentos más con una clave adicional, “age” por ejemplo:

> db.people.insert({ name: "Smith", age: 35 })
> db.people.insert({ name: "Peter", age: 18 })
> db.people.find()
{ "_id" : ObjectId("513cb9b3cd61c58d37e33e7e"), "name" : "Jose David" }
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cb9d3cd61c58d37e33e83"), "name" : "Fred" }
{ "_id" : ObjectId("513cb9decd61c58d37e33e84"), "name" : "George" }
{ "_id" : ObjectId("513cb9e2cd61c58d37e33e85"), "name" : "Will" }
{ "_id" : ObjectId("513cbb5dcd61c58d37e33e86"), "name" : 89 }
{ "_id" : ObjectId("513cbe46cd61c58d37e33e87"), "name" : "Smith", "age" : 35 }
{ "_id" : ObjectId("513cbe53cd61c58d37e33e88"), "name" : "Peter", "age" : 18 }

Ahora, se va a buscar sólo los documentos que posean la clave “age”:

> db.people.find({ age: { $exists: true } })
{ "_id" : ObjectId("513cbe46cd61c58d37e33e87"), "name" : "Smith", "age" : 35 }
{ "_id" : ObjectId("513cbe53cd61c58d37e33e88"), "name" : "Peter", "age" : 18 }

y ahora, los documentos que no poseen dicha clave:

> db.people.find({ age: { $exists: false } })
{ "_id" : ObjectId("513cb9b3cd61c58d37e33e7e"), "name" : "Jose David" }
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cb9d3cd61c58d37e33e83"), "name" : "Fred" }
{ "_id" : ObjectId("513cb9decd61c58d37e33e84"), "name" : "George" }
{ "_id" : ObjectId("513cb9e2cd61c58d37e33e85"), "name" : "Will" }
{ "_id" : ObjectId("513cbb5dcd61c58d37e33e86"), "name" : 89 }

Búsqueda por tipos

A la hora de realizar búsquedas se puede especificar el tipo de valor que debe contener la clave indicada. Esto es muy útil para casos en los que el campo clave en cuestión puede tener distintos tipos de valores y se desea encontrar sólo los de un tipo concreto.

Tal y como hemos anteriormente, el campo “name” de los documentos que se han insertado contienen cadenas de caracteres y valores numéricos. Para esto se usa el operador $type.

Para buscar sólo las cadenas de caracteres se usa el valor “2” que define el tipo “string”:

> db.people.find({ name: { $type: 2 } })
{ "_id" : ObjectId("513cb9b3cd61c58d37e33e7e"), "name" : "Jose David" }
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cb9d3cd61c58d37e33e83"), "name" : "Fred" }
{ "_id" : ObjectId("513cb9decd61c58d37e33e84"), "name" : "George" }
{ "_id" : ObjectId("513cb9e2cd61c58d37e33e85"), "name" : "Will" }
{ "_id" : ObjectId("513cbe46cd61c58d37e33e87"), "name" : "Smith", "age" : 35 }
{ "_id" : ObjectId("513cbe53cd61c58d37e33e88"), "name" : "Peter", "age" : 18 }

y para buscar valores numéricos el tipo “1”:

> db.people.find({ name: { $type: 1 } })
{ "_id" : ObjectId("513cbb5dcd61c58d37e33e86"), "name" : 89 }

Condicionales OR y AND

En la mayoría de búsquedas que se vayan a realizar será necesario buscar documentos en los que una determinada clave contenga un valor u otro o, en otros casos, un valor para una clave y otro para otra y así una larga lista de combinaciones entre tipos de valores, expresiones regulares, etc…

Para el condicional “or” se usa el operador “$or” y entre [] se encuentran los condicionales que se deben cumplir (al menos uno de ellos) para dar el documento como coincidente en dicha búsqueda.

Como primer ejemplo se va a ver la búsqueda de documentos cuyo valor de la clave “name” sea menor que “D” o posea la clave “age”:

> db.people.find({ $or: [ {name: { $lt: "F"}}, { age: { $exists: true }} ] })
{ "_id" : ObjectId("513cb9b7cd61c58d37e33e7f"), "name" : "Alice" }
{ "_id" : ObjectId("513cb9bccd61c58d37e33e80"), "name" : "Bob" }
{ "_id" : ObjectId("513cb9c0cd61c58d37e33e81"), "name" : "Charlie" }
{ "_id" : ObjectId("513cb9c8cd61c58d37e33e82"), "name" : "Dave" }
{ "_id" : ObjectId("513cbe46cd61c58d37e33e87"), "name" : "Smith", "age" : 35 }
{ "_id" : ObjectId("513cbe53cd61c58d37e33e88"), "name" : "Peter", "age" : 18 }

Para el condicional “and” se usa el operador “$and” y entre [] se encuentran los condicionales que se deben cumplir (todos y cada uno de ellos). En este caso se deben cumplir todas las condiciones para que el documento se trate como válido y, si alguna de ellas no se cumple, el documento es descartado.

Como ejemplo se va a ver un caso parecido al anterior ejemplo, buscando los documentos que empiecen por “S” en la clave “name” y posean el campo “age”:

> db.people.find({ $and: [ {name: { $regex: "^S"}}, { age: { $exists: true }} ] })
{ "_id" : ObjectId("513cbe46cd61c58d37e33e87"), "name" : "Smith", "age" : 35 }

Conclusión

Como se puede ver, se tienen gran cantidad de filtros de búsqueda en MongoDB y todos ellos de gran utilidad para cualquier desarrollo que se precie. Aunque controlar la sintaxis JSON con estos operadores parezca un poco tedioso al principio con un poco de práctica se controla sobradamente. Por tanto, os animo que practiquéis con los estos ejemplos para ir cogiendo soltura de cara a seguir con el tutorial de MongoDB con Java.

Happy Minds!!!

Share on FacebookTweet about this on TwitterShare on LinkedInShare on RedditShare on Google+Digg thisShare on TumblrPin on PinterestBuffer this pagePrint this pageEmail this to someone