MongoDB: Est-il possible de faire une requête insensible à la casse?

304

Exemple:

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

> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0
Luke Dennis
la source
3
Depuis MongoDB 3.2, vous pouvez exécuter une recherche insensible à la casse avec $caseSensitive: false. Voir: docs.mongodb.org/manual/reference/operator/query/text/…
martin
4
Notez que cela concerne uniquement les index de texte.
Willem D'Haeseleer
1
@martin: $caseSensitiveest déjà faux par défaut, et cela ne répond pas à la question, car cela ne fonctionne que sur les champs indexés. OP recherchait une comparaison de chaînes insensible à la casse.
Dan Dascalescu

Réponses:

343

Vous pouvez utiliser une expression régulière .

Dans votre exemple, ce serait:

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

Je dois dire, cependant, que vous pourriez peut-être simplement réduire (ou augmenter) la valeur en cours de route plutôt que d'engager des frais supplémentaires chaque fois que vous la trouvez. Évidemment, cela ne fonctionnera pas pour les noms des gens et autres, mais peut-être des cas d'utilisation comme des balises.

rfunduk
la source
27
Cela fonctionne parfaitement. Je l'ai fait fonctionner en PHP avec: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
Luke Dennis
2
Surtout si vous interpolez une chaîne ({foo: / # {x} / i}) qui pourrait contenir un point d'interrogation ..
Peter Ehrlich
17
N'oubliez pas non plus ^ et $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Julien
20
Notez que cela fera un balayage complet au lieu d'utiliser l'index.
Martin Konicek
12
il ne fera pas un fullscan s'il utilise l'ancre ^ au début, d'où l'importance des conseils de Julien.
Pax
198

METTRE À JOUR:

La réponse originale est désormais obsolète. Mongodb prend désormais en charge la recherche avancée en texte intégral, avec de nombreuses fonctionnalités.

RÉPONSE ORIGINALE:

Il convient de noter que la recherche avec la majuscule insensible à la casse de regex / i signifie que mongodb ne peut pas rechercher par index, donc les requêtes sur de grands ensembles de données peuvent prendre beaucoup de temps.

Même avec de petits ensembles de données, ce n'est pas très efficace. Vous prenez un coup de processeur beaucoup plus grand que vos garanties de requête, ce qui pourrait devenir un problème si vous essayez d'atteindre l'échelle.

Comme alternative, vous pouvez stocker une copie en majuscules et rechercher par rapport à cela. Par exemple, j'ai une table User qui a un nom d'utilisateur qui est une casse mixte, mais l'ID est une copie en majuscule du nom d'utilisateur. Cela garantit que la duplication sensible à la casse est impossible (avoir à la fois "Foo" et "foo" ne sera pas autorisé), et je peux rechercher par id = username.toUpperCase () pour obtenir une recherche insensible à la casse pour le nom d'utilisateur.

Si votre champ est grand, comme un corps de message, la duplication de données n'est probablement pas une bonne option. Je crois que l'utilisation d'un indexeur étranger comme Apache Lucene est la meilleure option dans ce cas.

Dan
la source
1
@Dan, juste pour l'info, dans la dernière MongoDB, "S'il existe un index pour le champ, alors MongoDB compare l'expression régulière aux valeurs de l'index, ce qui peut être plus rapide qu'une analyse de collection." - docs.mongodb.org/manual/reference/operator/query/regex/…
Sergiy Sokolenko
1
Les documents ont peut-être été mis à jour. Ils disent maintenant "Pour les requêtes d'expressions régulières sensibles à la casse, s'il existe un index pour le champ, alors MongoDB compare l'expression régulière aux valeurs de l'index, ce qui peut être plus rapide qu'une analyse de collection."
Jeff Lewis
1
Une autre limitation avec l'index de texte est que vous ne pouvez en avoir qu'une par collection (plusieurs colonnes), donc ne convient pas si vous devez isoler les recherches sur différents champs pour différents cas.
Paul Grimshaw
2
@SergiySokolenko: les docs disent maintenant (dernier paragraphe de la section ): "Les requêtes d'expressions régulières insensibles à la casse ne peuvent généralement pas utiliser efficacement les index. L'implémentation $ regex n'est pas sensible au classement et ne peut pas utiliser les index insensibles à la casse."
Dan Dascalescu
1
L'utilisation de la recherche en texte intégral est incorrecte dans ce cas (et potentiellement dangereuse ), car la question portait sur la création d'une requête insensible à la casse, par exemple la username: 'bill'correspondance BILLou Bill, pas une requête de recherche en texte intégral, qui correspondrait également aux mots dérivés de bill, tels que Bills, billedetc.
Dan Dascalescu
70

Si vous devez créer l'expression rationnelle à partir d'une variable, c'est une bien meilleure façon de le faire: https://stackoverflow.com/a/10728069/309514

Vous pouvez alors faire quelque chose comme:

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

Cela a l'avantage d'être plus programmatique ou vous pouvez obtenir une amélioration des performances en le compilant à l'avance si vous le réutilisez beaucoup.

Fotios
la source
1
new RegExp("^" + req.params.term.toLowerCase(), "i") fonctionne aussi très bien
Tahir Yasin
3
vous devriez envisager d'échapper la chaîne pour augmenter la sécurité si la variable provient d'une demande: stackoverflow.com/a/50633536/5195127
davidivad
À partir de MongoDB 3.4, il existe une prise en charge native des index insensibles à la casse
Dan Dascalescu
64

Gardez à l'esprit que l'exemple précédent:

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

fera en sorte que toutes les entrées contenant bar correspondent à la requête (bar1, barxyz, openbar), cela pourrait être très dangereux pour une recherche de nom d'utilisateur sur une fonction d'authentification ...

Vous devrez peut-être le faire correspondre uniquement au terme de recherche en utilisant la syntaxe d'expression rationnelle appropriée:

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

Voir http://www.regular-expressions.info/ pour obtenir de l'aide sur la syntaxe des expressions régulières

jflaflamme
la source
Cette réponse ressemble à un commentaire.
Dan Dascalescu
62

À partir de MongoDB 3.4, la méthode recommandée pour effectuer des recherches rapides insensibles à la casse consiste à utiliser un index insensible à la casse .

J'ai personnellement envoyé un e-mail à l'un des fondateurs pour que cela fonctionne, et il l'a fait! C'était un problème sur JIRA depuis 2009 , et beaucoup ont demandé la fonctionnalité. Voici comment ça fonctionne:

Un index insensible à la casse est créé en spécifiant un classement avec une force de 1 ou 2. Vous pouvez créer un index insensible à la casse comme ceci:

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

Vous pouvez également spécifier un classement par défaut par collection lorsque vous les créez:

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

Dans les deux cas, pour utiliser l'index insensible à la casse, vous devez spécifier le même classement dans l' findopération qui a été utilisée lors de la création de l'index ou de la collection:

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

Cela renverra "New York", "new york", "New york" etc.

Autres notes

  • Les réponses suggérant d'utiliser la recherche en texte intégral sont incorrectes dans ce cas (et potentiellement dangereuses ). La question était de faire une requête insensible à la casse, par exemple une username: 'bill'correspondance BILLou Billpas une requête de recherche en texte intégral, qui correspondrait également à des mots issus de bill, tels que Bills, billedetc.

  • Les réponses suggérant d'utiliser des expressions régulières sont lentes, car même avec des index, la documentation indique :

    "Les requêtes d'expressions régulières insensibles à la casse ne peuvent généralement pas utiliser efficacement les index. L'implémentation $ regex n'est pas sensible au classement et ne peut pas utiliser les index insensibles à la casse."

    $regexles réponses courent également le risque d’ injection d’entrée utilisateur .

user3413723
la source
A très bien fonctionné pour moi, même avec le pipeline d'agrégation.
Morio
Je pense que c'est la bonne réponse, car la vitesse de lecture des données est importante
Rndmax
Je n'arrive pas à trouver un moyen d'ajouter un classement par défaut à une collection une fois qu'elle a été créée. Existe-t-il un moyen de le faire?
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
la source
1
@ OlegV.Volkov doit avoir une description de la façon dont votre réponse est appropriée et de ce qui ne va pas dans le code de l'interrogateur.
Parth Trivedi
1
Cette réponse de code uniquement n'ajoute rien à la réponse acceptée, qui a été publiée 6 ans plus tôt.
Dan Dascalescu
19

TL; DR

Manière correcte de le faire en mongo

N'utilisez pas RegExp

Devenez naturel et utilisez l'indexation intégrée de Mongodb, recherchez

Étape 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 }
   ]
)
 

Étape 2 :

Besoin de créer un index sur le champ TEXTE que vous souhaitez rechercher, sans requête d'indexation sera extrêmement lent

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

étape 3 :

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


 
vijay
la source
1
Bonne option, mais il n'y a rien de plus "correct" dans l'utilisation d'un index de texte par rapport à une expression régulière, c'est juste une autre option. C'est exagéré pour le cas de l'OP.
JohnnyHK
2
Sauf que l'expression régulière est beaucoup plus lente. La recherche plein texte est également lente, mais pas aussi lente. Le moyen le plus rapide (mais le plus gonflé) serait un champ séparé qui est toujours défini en minuscules.
Tom Mettam
4
L'utilisation de la recherche en texte intégral est incorrecte dans ce cas (et potentiellement dangereuse ), car la question portait sur la création d'une requête insensible à la casse, par exemple la username: 'bill'correspondance BILLou Bill, pas une requête de recherche en texte intégral, qui correspondrait également aux mots dérivés de bill, tels que Bills, billedetc.
Dan Dascalescu
15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});
Nilesh
la source
2
Avez-vous regardé les réponses existantes avant de poster celle-ci? Au lieu d'une réponse quasi-dupliquée uniquement en code, vous voudrez peut-être expliquer comment elle ajoute quelque chose de valeur par rapport aux réponses précédentes.
Dan Dascalescu
1
Je veux juste ajouter que cette réponse m'a permis de trouver une solution. J'utilise un framework PHP et cela s'intègre bien dans la syntaxe ORM, contrairement aux autres solutions ici. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Don Rzeszut
9

Mongo (version actuelle 2.0.0) n'autorise pas les recherches non sensibles à la casse dans les champs indexés - voir leur documentation . Pour les champs non indexés, les expressions rationnelles répertoriées dans les autres réponses devraient être correctes.

Aidan Feldman
la source
19
Juste pour clarifier ceci: les recherches insensibles à la casse sont autorisées sur les champs indexés, elles n'utiliseront simplement pas l'index et seront aussi lentes que si le champ n'était pas indexé.
Heavi5ide
@ Heavi5ide puisque cette question est utilisée pour marquer les doublons, j'ai pensé clarifier que les expressions rationnelles (nécessaires pour les recherches insensibles à la casse) utilisent l'index, cependant, elles doivent effectuer une analyse complète de l'index. En d'autres termes, ils ne peuvent pas utiliser efficacement l'index. Heureusement, la documentation a depuis été mise à jour depuis 2011, mais il est toujours bon de le noter ici aussi.
Sammaye
7

Une chose très importante à garder à l'esprit lors de l'utilisation d'une requête basée sur Regex - Lorsque vous faites cela pour un système de connexion, échappez à chaque caractère que vous recherchez et n'oubliez pas les opérateurs ^ et $. Lodash a une fonction intéressante pour cela , si vous l'utilisez déjà:

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

Pourquoi? Imaginez un utilisateur entrant .*comme son nom d'utilisateur. Cela correspondrait à tous les noms d'utilisateur, permettant une connexion en devinant simplement le mot de passe de n'importe quel utilisateur.

Nick Kamer
la source
6

La meilleure méthode est dans la langue de votre choix, lorsque vous créez un wrapper de modèle pour vos objets, demandez à votre méthode save () de parcourir un ensemble de champs sur lesquels vous effectuerez une recherche qui sont également indexés; cet ensemble de champs doit avoir des équivalents en minuscules qui sont ensuite utilisés pour la recherche.

Chaque fois que l'objet est à nouveau enregistré, les propriétés en minuscules sont ensuite vérifiées et mises à jour avec toutes les modifications apportées aux propriétés principales. Cela vous permettra de rechercher efficacement, mais masquera le travail supplémentaire nécessaire pour mettre à jour les champs lc à chaque fois.

Les champs en minuscules peuvent être un magasin d'objets clé: valeur ou simplement le nom du champ avec un lc_ préfixé. J'utilise le second pour simplifier l'interrogation (l'interrogation d'objet profonde peut parfois prêter à confusion).

Remarque: vous souhaitez indexer les champs lc_, pas les champs principaux dont ils sont basés.

RobKohr
la source
Belle solution mais heureusement à partir de MongoDB 3.4, il existe un support natif pour les index insensibles à la casse .
Dan Dascalescu
6

Supposons que vous vouliez rechercher "colonne" dans "Table" et que vous vouliez une recherche sans distinction de casse. Le moyen le plus efficace et le plus efficace est le suivant;

//create empty JSON Object
mycolumn = {};

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

Le code ci-dessus ajoute simplement votre valeur de recherche en tant que RegEx et recherche avec des critères insensibles définis avec l'option "i".

Bonne chance.

Ankur Soni
la source
5

En utilisant Mongoose, cela a fonctionné pour moi:

var find = function(username, next){
    User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
        if(err) throw err;
        next(null, res);
    });
}
ChrisRich
la source
8
N'est-ce pas .toLowerCase()redondant si vous spécifiez l'indicateur insensible à la casse de i?
k00k
Oui, ça l'est. Vous n'avez pas besoin de .toLowerCase (). Je l'ai supprimé de la réponse.
ChrisRich
hmm cela devrait-il fonctionner comme ça? Lorsque je recherche "mark", il obtient également tous les enregistrements avec "marko".
Suisse
Ok je l'ai trouvé, l'expression correcte serait: '^' + serach_name + '$', "i"
Suisse
3
C'est dangereux. Vous n'échappez pas au nom d'utilisateur, donc toute expression rationnelle arbitraire peut être injectée.
Tom Mettam
3

Le cadre d'agrégation a été introduit dans mongodb 2.2. Vous pouvez utiliser l'opérateur de chaîne "$ strcasecmp" pour effectuer une comparaison insensible à la casse entre les chaînes. C'est plus recommandé et plus facile que d'utiliser regex.

Voici le document officiel sur l'opérateur de commande d'agrégation: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .

Jogue Wasin
la source
4
comment l'utiliser dans une requête find ()? db.stuff.find ({nom: $ strcasecmp (nom)})?
Suisse
3

Vous pouvez utiliser des index insensibles à la casse :

L'exemple suivant crée une collection sans classement par défaut, puis ajoute un index sur le champ de nom avec un classement insensible à la casse. Composants internationaux pour 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 } } )

Pour utiliser l'index, les requêtes doivent spécifier le même classement.

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 vous pouvez créer une collection avec un classement par défaut:

db.createCollection("users", { collation: { locale: 'tr', strength: 2 } } )
db.users.createIndex( { name : 1 } ) // inherits the default collation
Gencebay D.
la source
Il semble y avoir un problème de syntaxe mineur (accolades manquantes) .Mettez à jour la requête: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal
3

Pour rechercher une variable et l'échapper:

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

L'échappement de la variable protège la requête contre les attaques avec '. *' Ou une autre expression régulière.

escape-string-regexp

davidivad
la source
1

Utilisez RegExp , si aucune autre option ne fonctionne pour vous, RegExp est une bonne option. Cela rend la chaîne insensible à la casse.

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

utilisez le nom d'utilisateur dans les requêtes, puis c'est fait.

J'espère que cela fonctionnera aussi pour vous. Bonne chance.

Gouri Shankar Karanam
la source
0

J'ai créé un Func simple pour l'expression rationnelle insensible à la casse, que j'utilise dans mon filtre.

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

Ensuite, vous filtrez simplement sur un champ comme suit.

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

L'utilisation d'un filtre fonctionne pour moi 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();

Il peut même utiliser l'index car je pense que les méthodes sont appelées après le retour, mais je n'ai pas encore testé cela.

Cela évite également un problème de

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

que mongodb pensera que p.Title.ToLower () est une propriété et ne sera pas mappé correctement.

A_Arnold
la source
Merci, ça marche pour moi. Ici, nous devons obtenir un filtre en variable puis passer en méthode Find ().
Nilay
0

Pour tous ceux qui utilisent Golang et souhaitent avoir une recherche plein texte sensible à la casse avec mongodb et la bibliothèque mgo godoc globalsign .

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


err := collection.Find(query).Collation(collation)
Okandas
la source
-1

Comme vous pouvez le voir dans les documents mongo - puisque l' $textindex de la version 3.2 est insensible à la casse par défaut: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Créez un index de texte et utilisez l'opérateur $ text dans votre requête .

avalanche1
la source
L'utilisation de la recherche en texte intégral est incorrecte dans ce cas (et potentiellement dangereuse ), car la question portait sur la création d'une requête insensible à la casse, par exemple la username: 'bill'correspondance BILLou Bill, pas une requête de recherche en texte intégral, qui correspondrait également aux mots dérivés de bill, tels que Bills, billedetc.
Dan Dascalescu
-1

Ceux-ci ont été testés pour les recherches de chaînes

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

J'avais rencontré un problème similaire et c'est ce qui a fonctionné pour moi:

  const flavorExists = await Flavors.findOne({
    'flavor.name': { $regex: flavorName, $options: 'i' },
  });
Woppi
la source
Cette solution avait déjà été donnée deux fois auparavant. Veuillez vérifier les réponses existantes avant d'en publier une nouvelle.
Dan Dascalescu
@DanDascalescu ne sais pas de quoi vous parlez, sur CTRL + F, la solution similaire avec de nombreux votes positifs l'a publiée en septembre 2018. J'ai publié ma réponse en avril 2018. J'ai effectivement posté cela car il n'y en a pas à ce moment-là. Veuillez également vérifier quand il a été publié avant d'avertir ceux qui essaient vraiment d'aider.
Woppi
Je parle de cette réponse d'avril 2016 et de cette réponse de mai 2016. Les deux utilisent $regexet $options. Qu'avez-vous fait Ctrl + F?
Dan Dascalescu
En outre, l'utilisation $regexest inefficace et potentiellement non sécurisée, comme je l'ai expliqué dans ma modification de cette autre réponse de 2016 . Il n'y a aucune honte à supprimer les réponses si elles ne servent plus la communauté!
Dan Dascalescu
Noté sur $ regex inefficace, merci beaucoup. I Options Ctrl + F $. Nous ne sommes que deux ici sans nouvelle expression régulière dans notre code $ regex, avril 2018 et septembre 2018. Je n'ai pas utilisé de nouvelle expression régulière dans ma réponse. J'ai oublié le problème spécifique que j'ai rencontré avec la nouvelle expression rationnelle qui est résolu lorsque je l'ai supprimé et j'utilise simplement cette solution que j'ai publiée à la place.
Woppi