MongoDB: ¿Es posible hacer una consulta que no distinga entre mayúsculas y minúsculas?

304

Ejemplo:

> db.stuff.save({"foo":"bar"});

> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0
Luke Dennis
fuente
3
Desde MongoDB 3.2 puede ejecutar búsquedas sin distinción entre mayúsculas y minúsculas $caseSensitive: false. Ver: docs.mongodb.org/manual/reference/operator/query/text/…
martin
44
Tenga en cuenta que eso solo está en los índices de texto.
Willem D'Haeseleer
1
@martin: $caseSensitiveya es falso de forma predeterminada, y eso no responde a la pregunta, porque solo funciona en campos indexados. OP estaba buscando una comparación de cadenas que no distinga entre mayúsculas y minúsculas.
Dan Dascalescu

Respuestas:

343

Podrías usar una expresión regular .

En su ejemplo eso sería:

db.stuff.find( { foo: /^bar$/i } );

Sin embargo, debo decir que tal vez podría reducir (o aumentar) el valor en el camino en lugar de incurrir en un costo adicional cada vez que lo encuentre. Obviamente, esto no funcionará para los nombres de personas y tal, pero tal vez los casos de uso como etiquetas.

rfunduk
fuente
27
Esto funciona a la perfección. Lo tengo funcionando en PHP con: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
Luke Dennis el
2
Especialmente si estás interpolando una cadena ({foo: / # {x} / i}) que podría tener un signo de interrogación ..
Peter Ehrlich
17
No olvide también ^ y $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Julien
20
Tenga en cuenta que esto hará una exploración completa en lugar de utilizar el índice.
Martin Konicek
12
no hará un escaneo completo si usa el ancla ^ al principio, de ahí la importancia del consejo de Julien.
Pax
198

ACTUALIZAR:

La respuesta original ahora es obsoleta. Mongodb ahora admite la búsqueda avanzada de texto completo, con muchas funciones.

RESPUESTA ORIGINAL:

Cabe señalar que la búsqueda con mayúsculas y minúsculas de regex / i significa que mongodb no puede buscar por índice, por lo que las consultas contra grandes conjuntos de datos pueden llevar mucho tiempo.

Incluso con conjuntos de datos pequeños, no es muy eficiente. Obtiene un éxito de CPU mucho mayor que el que garantiza su consulta, lo que podría convertirse en un problema si está tratando de alcanzar la escala.

Como alternativa, puede almacenar una copia en mayúscula y buscar contra eso. Por ejemplo, tengo una tabla de usuario que tiene un nombre de usuario con mayúsculas y minúsculas, pero la identificación es una copia en mayúscula del nombre de usuario. Esto garantiza que la duplicación entre mayúsculas y minúsculas sea imposible (no se permitirá tener "Foo" y "foo"), y puedo buscar por id = username.toUpperCase () para obtener una búsqueda que no distinga entre mayúsculas y minúsculas para el nombre de usuario.

Si su campo es grande, como un cuerpo de mensaje, duplicar datos probablemente no sea una buena opción. Creo que usar un indexador extraño como Apache Lucene es la mejor opción en ese caso.

Dan
fuente
1
@Dan, solo para la información, en la última versión de MongoDB, "Si existe un índice para el campo, entonces MongoDB compara la expresión regular con los valores del índice, que pueden ser más rápidos que una exploración de recopilación". - docs.mongodb.org/manual/reference/operator/query/regex/…
Sergiy Sokolenko
1
Los documentos se han actualizado tal vez. Ahora dicen "Para consultas de expresión regular que distinguen entre mayúsculas y minúsculas, si existe un índice para el campo, entonces MongoDB compara la expresión regular con los valores del índice, que pueden ser más rápidos que una exploración de recopilación".
Jeff Lewis
1
Otra limitación con el índice de texto es que solo puede tener uno por colección (varias columnas), por lo que no es adecuado si necesita aislar búsquedas en diferentes campos para diferentes casos.
Paul Grimshaw
2
@SergiySokolenko: los documentos ahora dicen (último párrafo de la sección ): "Las consultas de expresión regular que no distinguen entre mayúsculas y minúsculas generalmente no pueden usar índices de manera efectiva. La implementación de $ regex no es compatible con la clasificación y no puede utilizar índices que no distinguen entre mayúsculas y minúsculas".
Dan Dascalescu
1
El uso de la búsqueda de texto completo es incorrecto en este caso (y potencialmente peligroso ), porque la pregunta era sobre hacer una consulta que no distinga entre mayúsculas y minúsculas, por ejemplo, username: 'bill'coincide BILLo Billno una consulta de búsqueda de texto completo, que también coincidiría con palabras derivadas de bill, como Bills, billedetc.
Dan Dascalescu
70

Si necesita crear la expresión regular a partir de una variable, esta es una forma mucho mejor de hacerlo: https://stackoverflow.com/a/10728069/309514

Luego puedes hacer algo como:

var string = "SomeStringToFind";
var regex = new RegExp(["^", string, "$"].join(""), "i");
// Creates a regex of: /^SomeStringToFind$/i
db.stuff.find( { foo: regex } );

Esto tiene el beneficio de ser más programático o puede obtener un aumento de rendimiento compilándolo con anticipación si lo está reutilizando mucho.

Fotios
fuente
1
new RegExp("^" + req.params.term.toLowerCase(), "i") también funciona bien
Tahir Yasin
2
debería considerar escapar de la cadena para aumentar la seguridad si la variable proviene de una solicitud: stackoverflow.com/a/50633536/5195127
davidivad
Comenzando con MongoDB 3.4, existe un soporte nativo para los índices de detección de mayúsculas
Dan Dascalescu
64

Tenga en cuenta que el ejemplo anterior:

db.stuff.find( { foo: /bar/i } );

hará que todas las entradas que contengan barra coincidan con la consulta (bar1, barxyz, barra libre), podría ser muy peligroso para una búsqueda de nombre de usuario en una función de autenticación ...

Es posible que deba hacer que coincida solo con el término de búsqueda utilizando la sintaxis de expresión regular apropiada como:

db.stuff.find( { foo: /^bar$/i } );

Consulte http://www.regular-expressions.info/ para obtener ayuda sobre la sintaxis de las expresiones regulares.

jflaflamme
fuente
Esta respuesta parece un comentario.
Dan Dascalescu
62

Comenzando con MongoDB 3.4, la forma recomendada de realizar búsquedas rápidas que no distinguen entre mayúsculas y minúsculas es utilizar un índice de detección de mayúsculas y minúsculas .

Personalmente le envié un correo electrónico a uno de los fundadores para que funcionara, ¡y él lo hizo posible! Era un problema en JIRA desde 2009 , y muchos han solicitado la función. Así es como funciona:

Se crea un índice que no distingue mayúsculas de minúsculas especificando una colación con una intensidad de 1 o 2. Puede crear un índice que no distinga mayúsculas de minúsculas como este:

db.cities.createIndex(
  { city: 1 },
  { 
    collation: {
      locale: 'en',
      strength: 2
    }
  }
);

También puede especificar una clasificación predeterminada por colección cuando los crea:

db.createCollection('cities', { collation: { locale: 'en', strength: 2 } } );

En cualquier caso, para utilizar el índice que no distingue entre mayúsculas y minúsculas, debe especificar la misma clasificación en la findoperación que se utilizó al crear el índice o la colección:

db.cities.find(
  { city: 'new york' }
).collation(
  { locale: 'en', strength: 2 }
);

Esto devolverá "Nueva York", "nueva york", "Nueva york", etc.

Otras notas

  • Las respuestas que sugieren utilizar la búsqueda de texto completo son incorrectas en este caso (y potencialmente peligrosas ). La pregunta era hacer una consulta que no distinga entre mayúsculas y minúsculas, por ejemplo, username: 'bill'coincidencia BILLo Billno una consulta de búsqueda de texto completo, que también coincidiría con palabras derivadas de bill, como Bills, billedetc.

  • Las respuestas que sugieren usar expresiones regulares son lentas, porque incluso con índices, la documentación dice :

    "Las consultas de expresión regular que no distinguen entre mayúsculas y minúsculas generalmente no pueden usar índices de manera efectiva. La implementación de $ regex no reconoce la intercalación y no puede utilizar índices que no distinguen entre mayúsculas y minúsculas".

    $regexLas respuestas también corren el riesgo de inyección de entrada del usuario .

usuario3413723
fuente
Funcionó muy bien para mí, incluso con la tubería de agregación.
Morio
Creo que esta es la respuesta correcta, porque la velocidad de lectura de datos es importante
Rndmax
Parece que no puedo encontrar ninguna manera de agregar una intercalación predeterminada a una colección una vez que se ha creado. ¿Hay alguna manera de hacerlo?
IncrediblePony
19
db.zipcodes.find({city : "NEW YORK"}); // Case-sensitive
db.zipcodes.find({city : /NEW york/i}); // Note the 'i' flag for case-insensitivity
rshivamca
fuente
1
@ OlegV.Volkov debe tener una descripción sobre cómo su respuesta es apropiada y qué está mal en el código del interrogador.
Parth Trivedi
1
Esta respuesta de solo código no agrega nada a la respuesta aceptada, que se publicó 6 años antes.
Dan Dascalescu
19

TL; DR

Forma correcta de hacer esto en mongo

No use RegExp

Vuélvase natural y use la indexación incorporada de mongodb, busque

Paso 1 :

db.articles.insert(
   [
     { _id: 1, subject: "coffee", author: "xyz", views: 50 },
     { _id: 2, subject: "Coffee Shopping", author: "efg", views: 5 },
     { _id: 3, subject: "Baking a cake", author: "abc", views: 90  },
     { _id: 4, subject: "baking", author: "xyz", views: 100 },
     { _id: 5, subject: "Café Con Leche", author: "abc", views: 200 },
     { _id: 6, subject: "Сырники", author: "jkl", views: 80 },
     { _id: 7, subject: "coffee and cream", author: "efg", views: 10 },
     { _id: 8, subject: "Cafe con Leche", author: "xyz", views: 10 }
   ]
)
 

Paso 2 :

Necesita crear un índice en cualquier campo de TEXTO que desee buscar, sin que la consulta de indexación sea extremadamente lenta

db.articles.createIndex( { subject: "text" } )

paso 3 :

db.articles.find( { $text: { $search: "coffee",$caseSensitive :true } } )  //FOR SENSITIVITY
db.articles.find( { $text: { $search: "coffee",$caseSensitive :false } } ) //FOR INSENSITIVITY


 
vijay
fuente
1
Buena opción, pero no hay nada más "correcto" sobre el uso de un índice de texto frente a una expresión regular, es solo otra opción. Es excesivo para el caso del OP.
JohnnyHK
2
Excepto que la expresión regular es significativamente más lenta. La búsqueda de texto completo también es lenta, pero no tan lenta. La forma más rápida (pero más hinchada) sería un campo separado que siempre se establece en minúsculas.
Tom Mettam
44
El uso de la búsqueda de texto completo es incorrecto en este caso (y potencialmente peligroso ), porque la pregunta era sobre hacer una consulta que no distinga entre mayúsculas y minúsculas, por ejemplo, username: 'bill'coincide BILLo Billno una consulta de búsqueda de texto completo, que también coincidiría con palabras derivadas de bill, como Bills, billedetc.
Dan Dascalescu
15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});
Nilesh
fuente
2
¿Has mirado las respuestas existentes antes de publicar esta? En lugar de una respuesta de código cuasi duplicado, es posible que desee explicar cómo agrega algo de valor en comparación con las respuestas anteriores.
Dan Dascalescu
1
Solo quiero agregar que esta respuesta es lo que me llevó a una solución. Estoy usando un marco PHP y esto encaja bien en la sintaxis ORM, mientras que las otras soluciones aquí no. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Don Rzeszut
9

Mongo (versión actual 2.0.0) no permite búsquedas sin distinción entre mayúsculas y minúsculas en campos indexados; consulte su documentación . Para los campos no indexados, las expresiones regulares enumeradas en las otras respuestas deberían estar bien.

Aidan Feldman
fuente
19
Solo para aclarar esto: se permiten búsquedas sin distinción entre mayúsculas y minúsculas en los campos indexados, simplemente no usarán el índice y serán tan lentos como si el campo no estuviera indexado.
heavi5ide
@ heavi5ide ya que esta pregunta se está utilizando para marcar duplicados, pensé que aclararía que las expresiones regulares (necesarias para búsquedas que no distinguen entre mayúsculas y minúsculas) usan el índice, sin embargo, deben hacer un escaneo de índice completo. En otras palabras, no pueden usar eficientemente el índice. Afortunadamente, la documentación se actualizó desde 2011, pero aún es bueno tenerla en cuenta aquí.
Sammaye
7

Una cosa muy importante a tener en cuenta al usar una consulta basada en Regex: cuando esté haciendo esto para un sistema de inicio de sesión, escape de todos los caracteres que está buscando y no olvide los operadores ^ y $. Lodash tiene una buena función para esto , si ya lo está usando:

db.stuff.find({$regex: new RegExp(_.escapeRegExp(bar), $options: 'i'})

¿Por qué? Imagine que un usuario ingresa .*como su nombre de usuario. Eso coincidiría con todos los nombres de usuario, permitiendo un inicio de sesión simplemente adivinando la contraseña de cualquier usuario.

Nick Kamer
fuente
6

El mejor método está en el idioma que elija, cuando cree un contenedor de modelo para sus objetos, haga que su método save () repita un conjunto de campos en los que buscará que también están indexados; ese conjunto de campos debe tener contrapartidas en minúsculas que luego se utilizan para la búsqueda.

Cada vez que el objeto se guarda nuevamente, las propiedades en minúsculas se verifican y actualizan con cualquier cambio en las propiedades principales. Esto hará que pueda buscar de manera eficiente, pero oculte el trabajo adicional necesario para actualizar los campos lc cada vez.

Los campos en minúsculas podrían ser una clave: valor de almacén de objetos o simplemente el nombre del campo con un prefijo lc_. Utilizo el segundo para simplificar las consultas (las consultas de objetos profundos pueden ser confusas a veces).

Nota: desea indexar los campos lc_, no los campos principales en los que se basan.

RobKohr
fuente
Buena solución, pero afortunadamente comenzando con MongoDB 3.4, existe un soporte nativo para los índices de detección de mayúsculas y minúsculas .
Dan Dascalescu
6

Supongamos que desea buscar "columna" en "Tabla" y desea una búsqueda que no distinga entre mayúsculas y minúsculas. La mejor y más eficiente forma es la siguiente;

//create empty JSON Object
mycolumn = {};

//check if column has valid value
if(column) {
    mycolumn.column = {$regex: new RegExp(column), $options: "i"};
}
Table.find(mycolumn);

El código anterior simplemente agrega su valor de búsqueda como RegEx y busca con criterios insensibles establecidos con "i" como opción.

Todo lo mejor.

Ankur Soni
fuente
5

Usando Mongoose esto funcionó para mí:

var find = function(username, next){
    User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
        if(err) throw err;
        next(null, res);
    });
}
ChrisRich
fuente
8
¿No es .toLowerCase()redundante si especificas el distintivo de mayúsculas y minúsculas de i?
k00k
Sí lo es. No necesita .toLowerCase (). Lo he eliminado de la respuesta.
ChrisRich
hmm, ¿debería funcionar así? Cuando busco "marca", también aparece cada registro con "marko". ¿Hay alguna forma de ignorar mayúsculas y minúsculas?
Suisse
Ok lo encontré, la expresión regular correcta sería: '^' + serach_name + '$', "i"
Suisse
3
Esto es peligroso. No escapa de nombre de usuario, por lo que se puede inyectar cualquier expresión regular arbitraria.
Tom Mettam
3

El marco de agregación se introdujo en mongodb 2.2. Puede usar el operador de cadena "$ strcasecmp" para hacer una comparación entre cadenas sin distinción entre mayúsculas y minúsculas. Es más recomendable y más fácil que usar regex.

Aquí está el documento oficial sobre el operador de comando de agregación: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .

Jogue Wasin
fuente
44
¿Cómo usar esto en una consulta find ()? db.stuff.find ({nombre: $ strcasecmp (nombre)})?
Suisse
3

Puede usar índices sin distinción entre mayúsculas y minúsculas :

El siguiente ejemplo crea una colección sin clasificación predeterminada, luego agrega un índice en el campo de nombre con una clasificación que no distingue entre mayúsculas y minúsculas. Componentes internacionales para Unicode

/* strength: CollationStrength.Secondary
* Secondary level of comparison. Collation performs comparisons up to secondary * differences, such as diacritics. That is, collation performs comparisons of 
* base characters (primary differences) and diacritics (secondary differences). * Differences between base characters takes precedence over secondary 
* differences.
*/
db.users.createIndex( { name: 1 }, collation: { locale: 'tr', strength: 2 } } )

Para usar el índice, las consultas deben especificar la misma clasificación.

db.users.insert( [ { name: "Oğuz" },
                            { name: "oğuz" },
                            { name: "OĞUZ" } ] )

// does not use index, finds one result
db.users.find( { name: "oğuz" } )

// uses the index, finds three results
db.users.find( { name: "oğuz" } ).collation( { locale: 'tr', strength: 2 } )

// does not use the index, finds three results (different strength)
db.users.find( { name: "oğuz" } ).collation( { locale: 'tr', strength: 1 } )

o puede crear una colección con clasificación predeterminada:

db.createCollection("users", { collation: { locale: 'tr', strength: 2 } } )
db.users.createIndex( { name : 1 } ) // inherits the default collation
Gencebay D.
fuente
Parece que hay un pequeño problema de sintaxis (faltan llaves). Actualice la consulta: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal
3

Para buscar una variable y escapar de ella:

const escapeStringRegexp = require('escape-string-regexp')
const name = 'foo'
db.stuff.find({name: new RegExp('^' + escapeStringRegexp(name) + '$', 'i')})   

Escapar de la variable protege la consulta contra ataques con '. *' U otra expresión regular.

escape-string-regexp

davidivad
fuente
1

Use RegExp , en caso de que alguna otra opción no funcione para usted, RegExp es una buena opción. Hace que la cadena no sea sensible.

var username = new RegExp("^" + "John" + "$", "i");;

use el nombre de usuario en las consultas, y luego está hecho.

Espero que funcione para ti también. Todo lo mejor.

Gouri Shankar Karanam
fuente
0

He creado un Func simple para la expresión regular sin distinción entre mayúsculas y minúsculas, que uso en mi filtro.

private Func<string, BsonRegularExpression> CaseInsensitiveCompare = (field) => 
            BsonRegularExpression.Create(new Regex(field, RegexOptions.IgnoreCase));

Luego, simplemente filtre en un campo de la siguiente manera.

db.stuff.find({"foo": CaseInsensitiveCompare("bar")}).count();
Nitesh
fuente
0

Usar un filtro me funciona en C #.

string s = "searchTerm";
    var filter = Builders<Model>.Filter.Where(p => p.Title.ToLower().Contains(s.ToLower()));
                var listSorted = collection.Find(filter).ToList();
                var list = collection.Find(filter).ToList();

Incluso puede usar el índice porque creo que los métodos se invocan después de la devolución, pero aún no lo he probado.

Esto también evita un problema de

var filter = Builders<Model>.Filter.Eq(p => p.Title.ToLower(), s.ToLower());

ese mongodb pensará que p.Title.ToLower () es una propiedad y no se asignará correctamente.

A_Arnold
fuente
Gracias, funciona para mí. Aquí necesitamos obtener un filtro en variable y luego pasar el método Find ().
Nilay
0

Para cualquiera que use Golang y desee tener una búsqueda de texto completo entre mayúsculas y minúsculas con mongodb y la biblioteca de mgo godoc globalsign .

collation := &mgo.Collation{
    Locale:   "en",
    Strength: 2, 
}


err := collection.Find(query).Collation(collation)
okandas
fuente
-1

Como puede ver en mongo docs, ya que el $textíndice de la versión 3.2 no distingue entre mayúsculas y minúsculas por defecto: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Cree un índice de texto y use el operador $ text en su consulta .

avalancha1
fuente
El uso de la búsqueda de texto completo es incorrecto en este caso (y potencialmente peligroso ), porque la pregunta era sobre hacer una consulta que no distinga entre mayúsculas y minúsculas, por ejemplo, username: 'bill'coincide BILLo Billno una consulta de búsqueda de texto completo, que también coincidiría con palabras derivadas de bill, como Bills, billedetc.
Dan Dascalescu
-1

Estos han sido probados para búsquedas de cadenas

{'_id': /.*CM.*/}               ||find _id where _id contains   ->CM
{'_id': /^CM/}                  ||find _id where _id starts     ->CM
{'_id': /CM$/}                  ||find _id where _id ends       ->CM

{'_id': /.*UcM075237.*/i}       ||find _id where _id contains   ->UcM075237, ignore upper/lower case
{'_id': /^UcM075237/i}          ||find _id where _id starts     ->UcM075237, ignore upper/lower case
{'_id': /UcM075237$/i}          ||find _id where _id ends       ->UcM075237, ignore upper/lower case
Ar maj
fuente
-1

Me enfrenté a un problema similar y esto es lo que funcionó para mí:

  const flavorExists = await Flavors.findOne({
    'flavor.name': { $regex: flavorName, $options: 'i' },
  });
Woppi
fuente
Esta solución ya se había dado dos veces antes. Verifique las respuestas existentes antes de publicar una nueva.
Dan Dascalescu
@DanDascalescu no está seguro de lo que está hablando, en CTRL + F, la solución similar con muchos votos positivos lo publicó en septiembre de 2018. Publiqué mi respuesta en abril de 2018. De hecho, publiqué esto porque no hay ninguno en ese momento. Compruebe también cuándo se publicó antes de advertir a quienes realmente intentan ayudar.
Woppi
Estoy hablando de esta respuesta a partir de abril de 2016, y esta respuesta de mayo de 2016. Tanto el consumo $regexy $options. ¿Qué hiciste Ctrl + F?
Dan Dascalescu
Además, el uso $regexes ineficiente y potencialmente inseguro, como he explicado en mi edición a esta otra respuesta de 2016 . ¡No hay vergüenza en eliminar respuestas si ya no sirven a la comunidad!
Dan Dascalescu
Tomado nota de ineficiente $ regex, muchas gracias. I Ctrl + F $ opciones. Solo somos dos aquí sin Regexp nuevo en nuestro código $ regex, abril de 2018 y septiembre de 2018. No utilicé Regexp nuevo en mi respuesta. Olvidé el problema específico que tenía con el nuevo Regexp que se resuelve cuando lo eliminé y solo uso esta solución que publiqué en su lugar.
Woppi