MongoDB: É possível fazer uma consulta que não diferencia maiúsculas de minúsculas?

304

Exemplo:

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

> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0
Luke Dennis
fonte
3
Desde o MongoDB 3.2, você pode executar uma pesquisa que não diferencia maiúsculas de minúsculas $caseSensitive: false. Veja: docs.mongodb.org/manual/reference/operator/query/text/…
martin
4
Observe que isso está apenas nos índices de texto.
Willem D'Haeseleer
1
@martin: $caseSensitivejá é falso por padrão, e isso não responde à pergunta, porque funciona apenas em campos indexados. O OP estava procurando uma comparação de cadeias sem distinção entre maiúsculas e minúsculas.
Dan Dascalescu

Respostas:

343

Você poderia usar um regex .

No seu exemplo, isso seria:

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

Devo dizer, no entanto, que talvez você deva apenas diminuir o valor (ou aumentar) o valor do caminho, em vez de incorrer no custo extra toda vez que o encontrar. Obviamente, isso não funcionará para nomes de pessoas e similares, mas talvez casos de uso como tags.

rfunduk
fonte
27
Isso funciona perfeitamente. Consegui trabalhar no PHP com: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
Luke Dennis
2
Especialmente se você estiver interpolando uma string ({foo: / # {x} / i}) que poderia ter um ponto de interrogação nele ..
Peter Ehrlich
17
Não se esqueça também ^ e $: MongoRegex ( '/ ^' preg_quote ($ val) '$ / i'..)
Julien
20
Observe que isso fará uma varredura completa em vez de usar o índice.
precisa
12
não fará uma varredura completa se ele usar a âncora ^ no início, daí a importância dos conselhos de Julien.
Pax
198

ATUALIZAR:

A resposta original agora está obsoleta. O Mongodb agora suporta pesquisa avançada de texto completo, com muitos recursos.

RESPOSTA ORIGINAL:

Deve-se notar que pesquisar com maiúsculas de minúsculas / i do regex significa que o mongodb não pode pesquisar por índice, portanto, consultas em conjuntos de dados grandes podem demorar muito tempo.

Mesmo com conjuntos de dados pequenos, não é muito eficiente. Você recebe um hit de CPU muito maior do que o exigido pela sua consulta, o que pode se tornar um problema se você estiver tentando obter escala.

Como alternativa, você pode armazenar uma cópia em maiúscula e pesquisar nela. Por exemplo, eu tenho uma tabela de usuário que possui um nome de usuário com letras maiúsculas e minúsculas, mas o id é uma cópia em maiúscula do nome de usuário. Isso garante que a duplicação com distinção entre maiúsculas e minúsculas é impossível (ter "Foo" e "foo" não serão permitidos), e eu posso pesquisar por id = nome_de_usuário.toUpperCase () para obter uma pesquisa que não diferencia maiúsculas de minúsculas de nome de usuário.

Se o seu campo for grande, como um corpo da mensagem, a duplicação de dados provavelmente não será uma boa opção. Acredito que usar um indexador estranho como o Apache Lucene é a melhor opção nesse caso.

Dan
fonte
1
@Dan, apenas pelas informações, no MongoDB mais recente, "Se existir um índice para o campo, o MongoDB corresponderá à expressão regular com os valores do índice, que podem ser mais rápidos que uma verificação de coleção." - docs.mongodb.org/manual/reference/operator/query/regex/…
Sergiy Sokolenko
1
Talvez os documentos tenham sido atualizados. Eles agora dizem "Para consultas de expressão regular com distinção entre maiúsculas e minúsculas, se existe um índice para o campo, o MongoDB faz a correspondência da expressão regular com os valores do índice, que podem ser mais rápidos que uma varredura de coleção".
Jeff Lewis
1
Outra limitação do índice de texto é que você pode ter apenas uma por coleção (várias colunas), portanto não é adequado se você precisar isolar pesquisas em campos diferentes para casos diferentes.
Paul Grimshaw
2
@SergiySokolenko: os documentos agora dizem (último parágrafo da seção ): "As consultas de expressão regular sem distinção entre maiúsculas e minúsculas geralmente não podem usar índices de maneira eficaz. A implementação $ regex não reconhece agrupamentos e não pode utilizar índices sem diferenciação entre maiúsculas e minúsculas."
Dan Dascalescu
1
Usando pesquisa de texto completo está errado neste caso (e potencialmente perigoso ), porque a pergunta era sobre como fazer uma consulta de maiúsculas e minúsculas, por exemplo, username: 'bill'correspondência BILLou Billnão uma consulta de pesquisa de texto completo, o que também jogo resultou palavras de bill, como Bills, billedetc.
Dan Dascalescu
70

Se você precisar criar o regexp a partir de uma variável, é uma maneira muito melhor de fazê-lo: https://stackoverflow.com/a/10728069/309514

Você pode fazer algo como:

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

Isso tem o benefício de ser mais programático ou você pode obter um aumento de desempenho compilando-o com antecedência, se estiver reutilizando muito.

Fotios
fonte
1
new RegExp("^" + req.params.term.toLowerCase(), "i") também funciona bem
Tahir Yasin 29/03
2
considere escapar da cadeia de caracteres para aumentar a segurança se a variável vier de uma solicitação: stackoverflow.com/a/50633536/5195127
davidivad
Começando com MongoDB 3.4, há suporte nativo para índices maiúsculas e minúsculas
Dan Dascalescu
64

Lembre-se de que o exemplo anterior:

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

fará com que todas as entradas que contenham bar correspondam à consulta (bar1, barxyz, openbar), pode ser muito perigoso para uma pesquisa de nome de usuário em uma função de autenticação ...

Pode ser necessário que ele corresponda apenas ao termo de pesquisa, usando a sintaxe regexp apropriada como:

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

Consulte http://www.regular-expressions.info/ para obter ajuda de sintaxe sobre expressões regulares

jflaflamme
fonte
Esta resposta parece um comentário.
Dan Dascalescu
62

A partir do MongoDB 3.4, a maneira recomendada de executar pesquisas rápidas que não diferenciam maiúsculas de minúsculas é usar um Índice Insensitivo a Maiúsculas .

Eu pessoalmente enviei um e-mail a um dos fundadores para que isso funcionasse, e ele fez isso acontecer! Foi um problema no JIRA desde 2009 e muitos solicitaram o recurso. Veja como funciona:

Um índice que não diferencia maiúsculas de minúsculas é feito especificando um agrupamento com uma força de 1 ou 2. Você pode criar um índice que não diferencia maiúsculas de minúsculas como este:

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

Você também pode especificar um agrupamento padrão por coleção ao criá-los:

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

Nos dois casos, para usar o índice que não diferencia maiúsculas de minúsculas, é necessário especificar o mesmo agrupamento na findoperação que foi usada ao criar o índice ou a coleção:

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

Isso retornará "Nova York", "Nova York", "Nova York" etc.

Outras notas

  • As respostas que sugerem usar a pesquisa de texto completo estão erradas nesse caso (e potencialmente perigosas ). A pergunta era sobre fazer uma consulta que não diferencia maiúsculas de minúsculas, por exemplo, username: 'bill'correspondência BILLou Billnão uma consulta de pesquisa de texto completo, que também corresponderia a palavras derivadas de bill, como Bills, billedetc.
  • As respostas sugerindo o uso de expressões regulares são lentas, porque mesmo com índices, a documentação afirma :

    "As consultas de expressão regular sem distinção entre maiúsculas e minúsculas geralmente não podem usar índices efetivamente. A implementação $ regex não reconhece agrupamentos e é incapaz de utilizar índices sem distinção entre maiúsculas e minúsculas."

    $regexAs respostas também correm o risco de injeção de entrada do usuário .

user3413723
fonte
Funcionou muito bem para mim, mesmo com o pipeline de agregação.
Morio
Eu acho que essa é a resposta certa, porque os dados de velocidade de leitura é importante
Rndmax
Não consigo encontrar nenhuma maneira de adicionar um agrupamento padrão a uma coleção depois que ela foi criada. Existe alguma maneira de fazer isso?
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
fonte
1
@ OlegV.Volkov deve ter uma descrição sobre como sua resposta é apropriada e o que está errado no código do questionador.
precisa
1
Essa resposta somente de código não adiciona nada à resposta aceita, publicada seis anos antes.
Dan Dascalescu 12/05/19
19

TL; DR

Maneira correta de fazer isso no mongo

Não use RegExp

Seja natural E use a indexação embutida do mongodb, pesquise

Passo 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 }
   ]
)

Passo 2 :

É necessário criar um índice no campo TEXT que você deseja pesquisar, sem que a consulta de indexação seja extremamente lenta

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

etapa 3 :

db.articles.find( { $text: { $search: "coffee",$caseSensitive :true } } )  //FOR SENSITIVITY
db.articles.find( { $text: { $search: "coffee",$caseSensitive :false } } ) //FOR INSENSITIVITY
vijay
fonte
1
Boa opção, mas não há nada mais "correto" em usar um índice de texto versus uma expressão regular, é apenas mais uma opção. É um exagero para o caso do OP.
precisa saber é o seguinte
2
Exceto regex, é significativamente mais lento. A pesquisa de texto completo também é lenta, mas não tão lenta. A maneira mais rápida (mas mais inchada) seria um campo separado, sempre definido como minúsculo.
Tom Mettam
4
Usando pesquisa de texto completo está errado neste caso (e potencialmente perigoso ), porque a pergunta era sobre como fazer uma consulta de maiúsculas e minúsculas, por exemplo, username: 'bill'correspondência BILLou Billnão uma consulta de pesquisa de texto completo, o que também jogo resultou palavras de bill, como Bills, billedetc.
Dan Dascalescu
15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});
Nilesh
fonte
2
Você já olhou as respostas existentes antes de postar esta? Em vez de uma resposta apenas de código quase duplicada, você pode explicar como ele adiciona algo de valor comparado às respostas anteriores.
Dan Dascalescu
1
Eu só quero acrescentar que esta resposta foi o que me levou a uma solução. Estou usando uma estrutura PHP e isso se encaixa bem na sintaxe do ORM, enquanto as outras soluções aqui não. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Don Rzeszut 12/09/19
9

O Mongo (versão atual 2.0.0) não permite pesquisas que diferenciam maiúsculas de minúsculas em campos indexados - consulte a documentação deles . Para campos não indexados, as expressões regulares listadas nas outras respostas devem estar corretas.

Aidan Feldman
fonte
19
Apenas para esclarecer isso: pesquisas sem distinção entre maiúsculas e minúsculas são permitidas em campos indexados, elas simplesmente não usarão o índice e serão tão lentas quanto se o campo não estivesse indexado.
heavi5ide
@ heavi5ide, já que esta pergunta está sendo usada para marcar duplicatas. Eu pensei em esclarecer que as expressões regulares (necessárias para pesquisas sem distinção entre maiúsculas e minúsculas) usam o índice, no entanto, elas devem fazer uma verificação completa do índice. Em outras palavras, eles não podem usar eficientemente o índice. Felizmente, a documentação foi atualizada desde 2011, mas ainda é bom observar aqui também.
Sammaye
7

Uma coisa muito importante a ter em mente ao usar uma consulta baseada em Regex - Ao fazer isso para um sistema de login, escape a cada caractere que você está procurando e não esqueça os operadores ^ e $. O Lodash tem uma boa função para isso , se você já o estiver usando:

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

Por quê? Imagine um usuário digitando .*como seu nome de usuário. Isso corresponderia a todos os nomes de usuário, permitindo um login apenas adivinhando a senha de qualquer usuário.

Nick Kamer
fonte
6

O melhor método está no seu idioma de escolha, ao criar um wrapper de modelo para seus objetos, faça com que o método save () itere através de um conjunto de campos nos quais você estará pesquisando que também são indexados; esses conjuntos de campos devem ter contrapartes em minúsculas que são usadas na pesquisa.

Sempre que o objeto é salvo novamente, as propriedades em minúsculas são verificadas e atualizadas com quaisquer alterações nas propriedades principais. Isso fará com que você possa pesquisar com eficiência, mas oculte o trabalho extra necessário para atualizar os campos lc a cada vez.

Os campos em minúsculas podem ser uma chave: armazenar objeto de valor ou apenas o nome do campo com um lc_ prefixado. Eu uso o segundo para simplificar a consulta (a consulta profunda a objetos pode ser confusa às vezes).

Nota: você deseja indexar os campos lc_, não os campos principais dos quais eles se baseiam.

RobKohr
fonte
Solução agradável, mas felizmente começando com o MongoDB 3.4, há suporte nativo para índices sem distinção entre maiúsculas e minúsculas .
Dan Dascalescu
6

Suponha que você queira pesquisar "coluna" em "Tabela" e deseje uma pesquisa sem distinção entre maiúsculas e minúsculas. A melhor e mais eficiente maneira é a seguinte;

//create empty JSON Object
mycolumn = {};

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

O código acima apenas adiciona seu valor de pesquisa como RegEx e pesquisa com critérios insensíveis definidos com "i" como opção.

Muito bem sucedida.

Ankur Soni
fonte
5

Usando o Mongoose, isso funcionou para mim:

var find = function(username, next){
    User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
        if(err) throw err;
        next(null, res);
    });
}
ChrisRich
fonte
8
O .toLowerCase()redundante não é se você estiver especificando o sinalizador que não diferencia maiúsculas de minúsculas i?
K00k
Sim, ele é. Você não precisa de .toLowerCase (). Eu o removi da resposta.
ChrisRich
hmm isso deve funcionar assim? Quando procuro por "mark", ele também obtém todos os registros com "marko" - existe uma maneira de ignorar apenas a distinção entre maiúsculas e minúsculas?
Suisse
Ok, encontrei, o regex correto seria: '^' + serach_name + '$', "i"
Suisse
3
Isso é perigoso. Você não está escapando do nome de usuário, portanto, qualquer regex arbitrário pode ser injetado.
Tom Mettam
3

A estrutura de agregação foi introduzida no mongodb 2.2. Você pode usar o operador de string "$ strcasecmp" para fazer uma comparação sem distinção entre maiúsculas e minúsculas. É mais recomendado e mais fácil do que usar regex.

Aqui está o documento oficial sobre o operador de comando de agregação: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .

Jogue Wasin
fonte
4
como usar isso em uma consulta find ()? db.stuff.find ({nome: $ strcasecmp (nome)})?
Suisse
3

Você pode usar índices sem distinção entre maiúsculas e minúsculas :

O exemplo a seguir cria uma coleção sem agrupamento padrão e adiciona um índice no campo de nome com um agrupamento sem distinção entre maiúsculas e minúsculas. Componentes internacionais 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 o índice, as consultas devem especificar o mesmo agrupamento.

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 } )

ou você pode criar uma coleção com agrupamento padrão:

db.createCollection("users", { collation: { locale: 'tr', strength: 2 } } )
db.users.createIndex( { name : 1 } ) // inherits the default collation
Gencebay D.
fonte
Não parece questão sintaxe menor (faltando chaves) .Por favor, atualize a consulta: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal
3

Para pesquisar uma variável e escapar dela:

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

Escapar da variável protege a consulta contra ataques com '. *' Ou outro regex.

escape-string-regexp

davidivad
fonte
1

Use RegExp . Caso outras opções não funcionem, o RegExp é uma boa opção. Isso torna a string insensível.

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

use o nome de usuário nas consultas e pronto.

Espero que funcione para você também. Muito bem sucedida.

Gouri Shankar Karanam
fonte
0

Eu criei um Func simples para o regex que não diferencia maiúsculas de minúsculas, que eu uso no meu filtro.

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

Então você simplesmente filtra em um campo da seguinte maneira.

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

Usar um filtro funciona para mim em 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();

Pode até usar o índice porque acredito que os métodos são chamados após o retorno, mas ainda não testei isso.

Isso também evita um problema de

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

que o mongodb pensará que p.Title.ToLower () é uma propriedade e não será mapeado corretamente.

A_Arnold
fonte
Obrigado, funciona para mim. Aqui precisamos obter o filtro na variável e depois passar no método Find ().
Nilay
0

Para quem usa Golang e deseja ter uma pesquisa de texto completo com distinção entre maiúsculas e minúsculas com o mongodb e a biblioteca mgo godoc globalsign .

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


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

Como você pode ver nos documentos do mongo - como o $textíndice da versão 3.2 não diferencia maiúsculas de minúsculas por padrão: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Crie um índice de texto e use o operador $ text na sua consulta .

avalanche1
fonte
Usando pesquisa de texto completo está errado neste caso (e potencialmente perigoso ), porque a pergunta era sobre como fazer uma consulta de maiúsculas e minúsculas, por exemplo, username: 'bill'correspondência BILLou Billnão uma consulta de pesquisa de texto completo, o que também jogo resultou palavras de bill, como Bills, billedetc.
Dan Dascalescu
-1

Estes foram testados para pesquisas de string

{'_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
fonte
-1

Eu já havia enfrentado um problema semelhante e foi isso que funcionou para mim:

  const flavorExists = await Flavors.findOne({
    'flavor.name': { $regex: flavorName, $options: 'i' },
  });
Woppi
fonte
Essa solução já havia sido dada duas vezes antes. Verifique as respostas existentes antes de postar uma nova.
Dan Dascalescu
O @DanDascalescu não tem certeza do que está falando, na CTRL + F, a solução semelhante com muitos upvotes postada em setembro de 2018. Postei minha resposta em abril de 2018. Na verdade, postei isso porque não havia nenhuma no momento. Verifique também quando foi publicado antes de avisar aqueles que realmente tentam ajudar.
Woppi 16/05/19
Eu estou falando sobre esta resposta a partir de abril de 2016, e esta resposta a partir de maio de 2016. Ambos uso $regexe $options. O que você Ctrl + F?
Dan Dascalescu 16/05/19
Além disso, o uso $regexé ineficiente e potencialmente inseguro, como expliquei na minha edição desta outra resposta de 2016 . Não há vergonha em excluir respostas se elas não servirem mais à comunidade!
Dan Dascalescu 16/05/19
Notável em $ regex ineficiente, muito obrigado. Ctrl + F $ opções. Somos apenas dois aqui sem novo Regexp em nosso código $ regex, abril de 2018 e setembro de 2018. Eu não usei o novo Regexp na minha resposta. Esqueci o problema específico que tive com o novo Regexp que foi resolvido quando o removi e apenas usei esta solução que publiquei.
Woppi 16/05/19