MongoDB: è possibile effettuare una query senza distinzione tra maiuscole e minuscole?


304

Esempio:

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

> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0

3
Da MongoDB 3.2 è possibile eseguire ricerche senza distinzione tra maiuscole e minuscole con $caseSensitive: false. Vedi: docs.mongodb.org/manual/reference/operator/query/text/…
martin

4
Nota che è solo sugli indici di testo.
Willem D'Haeseleer,

1
@martin: $caseSensitiveè già falso per impostazione predefinita e non risponde alla domanda, perché funziona solo su campi indicizzati. OP stava cercando un confronto di stringhe senza distinzione tra maiuscole e minuscole.
Dan Dascalescu,

Risposte:


343

Potresti usare una regex .

Nel tuo esempio sarebbe:

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

Devo dire, però, che potresti semplicemente ridurre (o aumentare) il valore lungo il percorso piuttosto che sostenere il costo aggiuntivo ogni volta che lo trovi. Ovviamente questo non funzionerà per i nomi delle persone e simili, ma forse casi d'uso come tag.


27
Funziona perfettamente. Funzionava in PHP con: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
Luke Dennis,

2
Soprattutto se stai interpolando una stringa ({foo: / # {x} / i}) che potrebbe avere un punto interrogativo al suo interno ..
Peter Ehrlich,

17
Non dimenticare anche ^ e $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Julien

20
Si noti che ciò eseguirà una scansione completa invece di utilizzare l'indice.
Martin Konicek,

12
non farà una scansione completa se all'inizio usa l'ancora ^, quindi l'importanza del consiglio di Julien.
Pax,

198

AGGIORNARE:

La risposta originale è ora obsoleta. Mongodb ora supporta la ricerca avanzata full-text, con molte funzionalità.

RISPOSTA ORIGINALE:

Va notato che la ricerca con maiuscole / minuscole regex significa che mongodb non può cercare per indice, quindi le query su insiemi di dati di grandi dimensioni possono richiedere molto tempo.

Anche con piccoli set di dati, non è molto efficiente. Prendi un hit cpu molto più grande dei tuoi quesiti, il che potrebbe diventare un problema se stai cercando di raggiungere la scala.

In alternativa, puoi archiviare una copia maiuscola e cercare in quella direzione. Ad esempio, ho una tabella utente con un nome utente che è un caso misto, ma l'id è una copia maiuscola del nome utente. Questo assicura che la duplicazione con distinzione tra maiuscole e minuscole sia impossibile (non saranno consentiti sia "Foo" che "foo"), e posso cercare per id = username.toUpperCase () per ottenere una ricerca senza distinzione tra maiuscole e minuscole per il nome utente.

Se il tuo campo è grande, come un corpo di messaggio, la duplicazione dei dati probabilmente non è una buona opzione. Credo che usare un indicizzatore estraneo come Apache Lucene sia l'opzione migliore in quel caso.


1
@Dan, solo per le informazioni, nell'ultimo MongoDB, "Se esiste un indice per il campo, MongoDB confronta l'espressione regolare con i valori dell'indice, che può essere più veloce di una scansione della raccolta." - docs.mongodb.org/manual/reference/operator/query/regex/…
Sergiy Sokolenko,

1
I documenti sono stati aggiornati forse. Ora dicono "Per le query con espressioni regolari con distinzione tra maiuscole e minuscole, se esiste un indice per il campo, MongoDB confronta l'espressione regolare con i valori nell'indice, che può essere più veloce di una scansione della raccolta."
Jeff Lewis,

1
Un'altra limitazione con l'indice di testo è che puoi averne solo una per raccolta (più colonne), quindi non è adatta se devi isolare ricerche su campi diversi per casi diversi.
Paul Grimshaw,

2
@SergiySokolenko: i documenti ora dicono (ultimo paragrafo nella sezione ): "Le query con espressioni regolari insensibili alle maiuscole non possono generalmente utilizzare gli indici in modo efficace. L'implementazione di $ regex non è sensibile alle regole di confronto e non è in grado di utilizzare indici insensibili al maiuscolo / minuscolo."
Dan Dascalescu,

1
Utilizzando la ricerca full-text è sbagliato in questo caso (e potenzialmente pericoloso ), perché la questione era di fare una query case-insensitive, ad esempio username: 'bill'corrispondenza BILLo Bill, non una query di ricerca full-text, che sarebbe anche partita gambo parole di bill, come ad esempio Bills, billedecc.
Dan Dascalescu,

70

Se devi creare il regexp da una variabile, questo è un modo molto migliore per farlo: https://stackoverflow.com/a/10728069/309514

Puoi quindi fare qualcosa del tipo:

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

Questo ha il vantaggio di essere più programmatico o puoi ottenere un aumento delle prestazioni compilandolo in anticipo se lo stai riutilizzando molto.


1
new RegExp("^" + req.params.term.toLowerCase(), "i") funziona anche bene
Tahir Yasin

2
si dovrebbe considerare sfuggire la stringa per aumentare la sicurezza se la variabile viene da una richiesta: stackoverflow.com/a/50633536/5195127
davidivad

A partire da MongoDB 3.4, esiste il supporto nativo per gli indici case insensitive
Dan Dascalescu,

64

Tieni presente che l'esempio precedente:

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

farà sì che ogni voce contenente la barra corrisponda alla query (bar1, barxyz, openbar), potrebbe essere molto pericoloso per una ricerca nome utente su una funzione di autenticazione ...

Potrebbe essere necessario farlo corrispondere solo al termine di ricerca utilizzando la sintassi regexp appropriata come:

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

Vedi http://www.regular-expressions.info/ per aiuto sulla sintassi delle espressioni regolari


Questa risposta sembra un commento.
Dan Dascalescu,

62

A partire da MongoDB 3.4, il modo consigliato per eseguire ricerche veloci senza distinzione tra maiuscole e minuscole è utilizzare un indice insensibile alle maiuscole .

Ho mandato personalmente uno dei fondatori per favore per far funzionare tutto questo, e l'ha fatto accadere! È stato un problema su JIRA dal 2009 e molti hanno richiesto la funzione. Ecco come funziona:

Un indice senza distinzione tra maiuscole e minuscole viene creato specificando una fascicolazione con un valore di 1 o 2. È possibile creare un indice senza distinzione tra maiuscole e minuscole in questo modo:

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

Puoi anche specificare un confronto predefinito per raccolta quando li crei:

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

In entrambi i casi, per utilizzare l'indice senza distinzione tra maiuscole e minuscole, è necessario specificare le stesse regole di confronto findnell'operazione utilizzata durante la creazione dell'indice o della raccolta:

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

Ciò restituirà "New York", "New York", "New York" ecc.

Altre note

  • Le risposte che suggeriscono di utilizzare la ricerca full-text sono errate in questo caso (e potenzialmente pericolose ). La domanda riguardava la creazione di una query senza distinzione tra maiuscole e minuscole, ad esempio la username: 'bill'corrispondenza BILLo Bill, non una query di ricerca full-text, che corrisponderebbe anche a parole derivatebill , come Bills, billedecc.

  • Le risposte che suggeriscono di usare espressioni regolari sono lente, perché anche con gli indici, la documentazione afferma :

    "Le query con espressioni regolari senza distinzione tra maiuscole e minuscole in genere non possono utilizzare gli indici in modo efficace. L'implementazione di $ regex non è sensibile alle regole di confronto e non è in grado di utilizzare indici senza distinzione tra maiuscole e minuscole."

    $regexle risposte corrono anche il rischio di iniezione da parte dell'utente .


Ha funzionato alla grande per me, anche con la pipeline di aggregazione.
Morio

Penso che questa sia la risposta giusta, perché la velocità di lettura dei dati è importante
Rndmax l'

Non riesco a trovare alcun modo per aggiungere un confronto predefinito a una raccolta una volta che è stata creata. C'è un modo per farlo?
IncrediblePony,

19
db.zipcodes.find({city : "NEW YORK"}); // Case-sensitive
db.zipcodes.find({city : /NEW york/i}); // Note the 'i' flag for case-insensitivity

1
@ OlegV.Volkov deve avere una descrizione di come la risposta è appropriata e cosa c'è di sbagliato nel codice dell'interrogante.
Parth Trivedi,

1
Questa risposta di solo codice non aggiunge nulla a quella accettata, pubblicata 6 anni prima.
Dan Dascalescu,

19

TL; DR

Modo corretto per farlo in mongo

Non utilizzare RegExp

Diventa naturale E usa l'indicizzazione integrata di mongodb, cerca

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 :

È necessario creare un indice su qualsiasi campo TEXT che si desidera cercare, senza che la query di indicizzazione sia estremamente lenta

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

passaggio 3:

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


 

1
Buona opzione, ma non c'è niente di più "corretto" nell'uso di un indice di testo rispetto a una regex, è solo un'altra opzione. È eccessivo per il caso del PO.
JohnnyHK,

2
Tranne che regex è significativamente più lento. Anche la ricerca full-text è lenta, ma non così lenta. Il modo più veloce (ma più gonfio) sarebbe un campo separato che è sempre impostato in minuscolo.
Tom Mettam,

4
Utilizzando la ricerca full-text è sbagliato in questo caso (e potenzialmente pericoloso ), perché la questione era di fare una query case-insensitive, ad esempio username: 'bill'corrispondenza BILLo Bill, non una query di ricerca full-text, che sarebbe anche partita gambo parole di bill, come ad esempio Bills, billedecc.
Dan Dascalescu,

15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});

2
Hai esaminato le risposte esistenti prima di pubblicare questo? Invece di una risposta di solo codice quasi duplicata, potresti voler spiegare come aggiungere qualcosa di valore rispetto alle risposte precedenti.
Dan Dascalescu,

1
Voglio solo aggiungere che questa risposta è ciò che mi ha portato a una soluzione. Sto usando un framework PHP e questo si adatta bene alla sintassi ORM mentre le altre soluzioni qui non lo hanno fatto. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Don Rzeszut,

9

Mongo (versione corrente 2.0.0) non consente ricerche senza distinzione tra maiuscole e minuscole rispetto ai campi indicizzati: consulta la loro documentazione . Per i campi non indicizzati, le regex elencate nelle altre risposte dovrebbero andare bene.


19
Solo per chiarire questo: le ricerche senza distinzione tra maiuscole e minuscole sono consentite sui campi indicizzati, semplicemente non useranno l'indice e saranno lente come se il campo non fosse indicizzato.
heavi5ide,

@ heavi5ide poiché questa domanda viene utilizzata per contrassegnare i duplicati, ho pensato di chiarire che le regex (necessarie per ricerche insensibili al maiuscolo / minuscolo) utilizzano l'indice, tuttavia devono eseguire una scansione completa dell'indice. In altre parole, non possono utilizzare in modo efficiente l'indice. Fortunatamente la documentazione è stata aggiornata dal 2011, ma è comunque utile notare anche qui.
Sammaye,

7

Una cosa molto importante da tenere a mente quando si utilizza una query basata su Regex: quando si esegue questa operazione per un sistema di accesso, sfuggire a ogni singolo carattere che si sta cercando e non dimenticare gli operatori ^ e $. Lodash ha una bella funzione per questo , se lo stai già usando:

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

Perché? Immagina un utente che entra .*come suo nome utente. Ciò corrisponderebbe a tutti i nomi utente, abilitando un accesso semplicemente indovinando la password di qualsiasi utente.


6

Il metodo migliore è nella tua lingua preferita, quando crei un wrapper modello per i tuoi oggetti, fai in modo che il tuo metodo save () esegua l'iterazione attraverso una serie di campi su cui stai cercando che sono anche indicizzati; tali set di campi devono avere controparti minuscole che vengono quindi utilizzate per la ricerca.

Ogni volta che l'oggetto viene nuovamente salvato, le proprietà minuscole vengono quindi controllate e aggiornate con eventuali modifiche alle proprietà principali. Ciò consentirà di effettuare ricerche in modo efficiente, ma di nascondere il lavoro aggiuntivo necessario per aggiornare i campi LC ogni volta.

I campi minuscoli potrebbero essere una chiave: valore oggetto archivio o solo il nome del campo con un prefisso lc_. Uso il secondo per semplificare l'interrogazione (a volte l'interrogazione di oggetti profondi può creare confusione).

Nota: si desidera indicizzare i campi lc_, non i campi principali su cui sono basati.


Bella soluzione ma fortunatamente a partire da MongoDB 3.4, esiste il supporto nativo per gli indici case insensitive .
Dan Dascalescu,

6

Supponiamo di voler cercare "colonna" in "Tabella" e di cercare la ricerca senza distinzione tra maiuscole e minuscole. Il modo migliore ed efficiente è il seguente;

//create empty JSON Object
mycolumn = {};

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

Il codice sopra aggiunge semplicemente il valore di ricerca come RegEx e cerca con criteri insensibili impostati con "i" come opzione.

Ti auguro il meglio.


5

Usando Mongoose ha funzionato per me:

var find = function(username, next){
    User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
        if(err) throw err;
        next(null, res);
    });
}

8
Non è .toLowerCase()ridondante se si specifica la bandiera senza distinzione tra maiuscole e minuscole i?
k00k,

Sì. Non è necessario .toLowerCase (). L'ho rimosso dalla risposta.
ChrisRich,

hmm dovrebbe funzionare così? Quando cerco "mark" ottiene anche tutti i record con "marko" - c'è un modo di ignorare la distinzione tra maiuscole e minuscole?
Suisse,

Ok l'ho trovato, la regex corretta sarebbe: '^' + serach_name + '$', "i"
Suisse,

3
Questo è pericoloso. Non stai sfuggendo al nome utente, quindi qualsiasi regex arbitraria può essere iniettata.
Tom Mettam,

3

Il framework di aggregazione è stato introdotto in mongodb 2.2. È possibile utilizzare l'operatore di stringa "$ strcasecmp" per effettuare un confronto senza distinzione tra maiuscole e minuscole. È più consigliato e più semplice rispetto all'utilizzo di regex.

Ecco il documento ufficiale sull'operatore del comando di aggregazione: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .


4
come usarlo in una query find ()? db.stuff.find ({name: $ strcasecmp (name)})?
Suisse,

3

È possibile utilizzare gli indici insensibili alle maiuscole :

L'esempio seguente crea una raccolta senza regole di confronto predefinite, quindi aggiunge un indice sul campo del nome con regole di confronto senza distinzione tra maiuscole e minuscole. Componenti internazionali per 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 } } )

Per utilizzare l'indice, le query devono specificare le stesse regole di confronto.

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

oppure puoi creare una raccolta con regole di confronto predefinite:

db.createCollection("users", { collation: { locale: 'tr', strength: 2 } } )
db.users.createIndex( { name : 1 } ) // inherits the default collation

Sembra esserci un piccolo problema di sintassi (parentesi graffe mancanti). Aggiorna la query: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal,

3

Per cercare una variabile e sfuggirla:

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

L'escape della variabile protegge la query dagli attacchi con '. *' O altri regex.

fuga-string-regexp


1

Usa RegExp , nel caso in cui qualsiasi altra opzione non funzioni per te, RegExp è una buona opzione. Rende la distinzione tra maiuscole e minuscole.

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

usa il nome utente nelle query e poi è fatto.

Spero che funzionerà anche per te. Ti auguro il meglio.


0

Ho creato un semplice Func per il regex insensibile al maiuscolo / minuscolo, che uso nel mio filtro.

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

Quindi si filtra semplicemente su un campo come segue.

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

0

L'uso di un filtro funziona per me in 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();

Potrebbe persino utilizzare l'indice perché credo che i metodi vengano chiamati dopo che si è verificato il ritorno, ma non l'ho ancora testato.

Questo evita anche un problema di

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

quel mongodb penserà che p.Title.ToLower () sia una proprietà e non verrà mappata correttamente.


Grazie, funziona per me. Qui abbiamo bisogno di ottenere il filtro in variabile quindi passare nel metodo Find ().
Nilay,

0

Per chiunque utilizzi Golang e desidera avere la ricerca di testo completo maiuscole e minuscole con mongodb e la libreria mgo godoc globalsign .

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


err := collection.Find(query).Collation(collation)

-1

Come puoi vedere nei documenti di mongo, poiché l' $textindice della versione 3.2 non fa distinzione tra maiuscole e minuscole per impostazione predefinita: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Crea un indice di testo e usa $ text operator nella tua query .


Utilizzando la ricerca full-text è sbagliato in questo caso (e potenzialmente pericoloso ), perché la questione era di fare una query case-insensitive, ad esempio username: 'bill'corrispondenza BILLo Bill, non una query di ricerca full-text, che sarebbe anche partita gambo parole di bill, come ad esempio Bills, billedecc.
Dan Dascalescu,

-1

Questi sono stati testati per le ricerche di stringhe

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

-1

Avevo affrontato un problema simile e questo è ciò che ha funzionato per me:

  const flavorExists = await Flavors.findOne({
    'flavor.name': { $regex: flavorName, $options: 'i' },
  });

Questa soluzione era già stata data due volte prima. Controlla le risposte esistenti prima di pubblicarne una nuova.
Dan Dascalescu

@DanDascalescu non sono sicuro di cosa tu stia parlando, su CTRL + F, la soluzione simile con molti voti lo ha pubblicato a settembre 2018. Ho pubblicato la mia risposta ad aprile 2018. In realtà l'ho pubblicato perché non ce n'è nessuno in quel momento. Controlla anche quando è stato pubblicato prima di avvisare coloro che hanno semplicemente cercato di aiutarti.
Woppi,

Sto parlando di questa risposta di aprile 2016 e di questa risposta di maggio 2016. Entrambi usano $regexe $options. Cos'hai fatto Ctrl + F?
Dan Dascalescu

Inoltre, l'utilizzo $regexè inefficiente e potenzialmente insicuro, come ho spiegato nella mia modifica a questa altra risposta del 2016 . Non c'è vergogna nell'eliminare le risposte se non servono più alla comunità!
Dan Dascalescu

Notato su $ regex inefficiente, grazie mille. I Ctrl + F $ opzioni. Siamo solo in due qui senza nuovo Regexp nel nostro codice $ regex, aprile 2018 e settembre 2018. Non ho usato il nuovo Regexp nella mia risposta. Ho dimenticato il problema specifico che ho riscontrato con il nuovo Regexp che è stato risolto quando l'ho rimosso e ho usato invece questa soluzione che ho pubblicato.
Woppi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.