MongoDB: Чи можливо зробити запит, що не враховує регістр?


304

Приклад:

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

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

3
Оскільки MongoDB 3.2 ви можете виконати пошук, нечутливий до регістру $caseSensitive: false. Див: docs.mongodb.org/manual/reference/operator/query/text / ...
Мартіну

4
Зауважте, що це лише на текстових індексах.
Віллем Д'Хассілер

1
@martin: $caseSensitivefalse вже за замовчуванням, і це не відповідає на питання, оскільки працює лише в індексованих полях. ОП шукала порівняння рядків з нечутливим до випадків.
Дан Даскалеску

Відповіді:


342

Ви можете використовувати регулярний вираз .

У вашому прикладі це було б:

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

Треба сказати, хоча, можливо, ви могли просто зменшити (або збільшити) значення на шляху, а не нести додаткові витрати щоразу, коли знайдете його. Очевидно, це не працює для імен людей і таких, але, можливо, випадків використання, як теги.


27
Це прекрасно працює. Зробив це в PHP за допомогою: $ collection-> find (array ('key' => новий MongoRegex ('/'.$ val.' / I ')));
Люк Денніс

2
Особливо, якщо ви інтерполюєте рядок ({foo: / # {x} / i}), яка могла б мати в ньому знак питання ..
Пітер Ерліх

17
Не забувайте також ^ і $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Жульєн

20
Зауважте, що це зробить повний скан замість використання індексу.
Мартін Конічек

12
це не зробить повний скан, якщо він використовує ^ якор на початку, звідси важлива порада Жульєна.
Пакс

198

ОНОВЛЕННЯ:

Оригінальна відповідь зараз застаріла. Зараз Mongodb підтримує розширений повний пошук тексту з багатьма функціями.

ОРИГІНАЛЬНИЙ ВІДПОВІДЬ:

Слід зазначити, що пошук з регістровим регістром / i означає, що mongodb не може здійснювати пошук за індексами, тому запити проти великих наборів даних можуть зайняти багато часу.

Навіть з невеликими наборами даних це не дуже ефективно. Ви отримуєте набагато більший хіт процесора, ніж ваші запити, що може стати проблемою, якщо ви намагаєтеся досягти масштабу.

Як альтернативу, ви можете зберігати велику копію та шукати її. Наприклад, у мене є таблиця користувача, у якій є ім'я користувача, яке є змішаним регістром, але ідентифікатор - це велика копія імені користувача. Це забезпечує неможливе копіювання з урахуванням регістру (мати і "Foo", і "foo" не буде дозволено), і я можу шукати за id = username.toUpperCase (), щоб отримати нечутливий до регістру пошук імені користувача.

Якщо ваше поле велике, наприклад, тіло повідомлення, дублювання даних, мабуть, не є хорошим варіантом. Я вважаю, що використання стороннього індексатора, як Apache Lucene, є найкращим варіантом у цьому випадку.


1
@Dan, просто для інформації, в останньому MongoDB: "Якщо індекс існує для поля, то MongoDB відповідає регулярному вираженню проти значень в індексі, що може бути швидшим, ніж сканування колекції". - docs.mongodb.org/manual/reference/operator/query/regex/…
Сергій Соколенко

1
Документи були оновлені, можливо. Тепер вони кажуть: "Для запитів регулярних виразів з урахуванням регістру, якщо для поля існує індекс, то MongoDB співпадає з регулярним виразом проти значень в індексі, що може бути швидшим, ніж сканування колекції".
Джефф Льюїс

1
Ще одне обмеження щодо текстового покажчика полягає в тому, що ви можете мати лише один на колекцію (кілька стовпців), тому не підходить, якщо вам потрібно ізолювати пошук у різних полях для різних випадків.
Пол Грімшоу

2
@SergiySokolenko: Документи зараз кажуть (останній абзац у розділі ): "Запити регулярних виразів у випадку нечутливих до випадків випадків не можуть ефективно використовувати індекси. Реалізація $ regex не відома порівняння та не може використовувати індекси нечутливих до регістру".
Дан Даскалеску

1
Використання повнотекстового пошуку неправильно в цьому випадку (і потенційно небезпечно ), тому що мова йшла про прийняття запиту до регістру, наприклад , username: 'bill'узгодження BILLабо BillНЕ пошуковий запит повного тексту, який буде також матч стебел слова з bill, наприклад, Bills, і billedт.д.
Dan Dăscălescu

70

Якщо вам потрібно створити регулярну форму з змінної, це набагато кращий спосіб зробити це: https://stackoverflow.com/a/10728069/309514

Потім ви можете зробити щось на кшталт:

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

Це має перевагу бути більш програмним, або ви можете отримати підвищення продуктивності, склавши його достроково, якщо ви багаторазово використовуєте його.


1
new RegExp("^" + req.params.term.toLowerCase(), "i") також чудово працює
Тахір Ясін

2
Ви повинні розглянути рятуючись рядком для підвищення рівня безпеки , якщо змінні приходить із запиту: stackoverflow.com/a/50633536/5195127
davidivad

Починаючи з MongoDB 3.4, існує вбудована підтримка індексів
чутливості

64

Майте на увазі, що попередній приклад:

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

призведе до того, що всі записи, що містять бар, відповідають запиту (bar1, barxyz, openbar), це може бути дуже небезпечно для пошуку імені користувача на функції авт ...

Вам може знадобитися, щоб він відповідав лише пошуковому терміну, використовуючи відповідний синтаксис regexp як:

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

Див. Http://www.regular-expressions.info/ щодо довідок щодо синтаксису щодо регулярних виразів


Ця відповідь виглядає як коментар.
Дан Даскалеску

62

Починаючи з MongoDB 3.4, рекомендованим способом швидкого пошуку нечутливих до регістру запитів є використання індексу чутливості випадків .

Я особисто надіслав електронною поштою одному із засновників, щоб просити його працювати, і він це зробив! Це було проблемою JIRA з 2009 року , і багато хто попросив цю функцію. Ось як це працює:

Індекс, нечутливий до регістру, визначається шляхом визначення зіставлення з рівнем 1 або 2. Ви можете створити нечутливий до регістру індекс так:

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

Ви також можете вказати порівняння за замовчуванням для колекції під час їх створення:

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

В будь-якому випадку, щоб використовувати індекс, нечутливий до регістру, потрібно вказати той самий порівняння в findоперації, який був використаний під час створення індексу або колекції:

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

Це поверне «Нью-Йорк», «Нью-Йорк», «Нью-Йорк» тощо.

Інші примітки

  • Відповіді, що пропонують використовувати повнотекстовий пошук, в цьому випадку помилкові (і потенційно небезпечні ). Питання було про те , регістронезавісімого запит, наприклад , username: 'bill'узгодження BILLабо Bill, а не повний текст пошукового запиту, який буде також відповідати стеблах слова bill, наприклад Bills, і billedт.д.
  • Відповіді, що пропонують використовувати регулярні вирази, є повільними, оскільки навіть в індексах документація зазначає :

    "Запити регулярних виразів нечутливих до регістрів, як правило, не можуть ефективно використовувати індекси. Реалізація $ regex не відома для порівняння та не може використовувати індекси нечутливих до регістрів."

    $regexвідповіді також ризикують ввести введення користувачем інформації .


Для мене чудово працювали навіть із конвеєрним конвеєром.
Моріо

Я думаю, що це правильна відповідь, тому що важлива швидкість читання даних
Rndmax

Здається, я не можу знайти жодного способу додати порівняння за замовчуванням до колекції, коли воно було створене. Чи можна це зробити?
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
@ Олег В.Волков повинен мати опис того, як відповідь відповідна і що не так у коді запитувача.
Parth Trivedi

1
Ця відповідь лише для коду нічого не додає до прийнятої, яка була розміщена на 6 років раніше.
Дан Даскалеску

19

TL; DR

Правильно це зробити в монго

Не використовуйте RegExp

Переходьте до природного та використовуйте вбудовану індексацію mongodb, пошук

Крок 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 }
   ]
)

Крок 2:

Потрібно створити індекс у будь-якому полі TEXT, яке ви хочете шукати, без індексування запит буде надзвичайно повільним

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

крок 3:

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

1
Хороший варіант, але немає нічого більш "правильного" щодо використання текстового покажчика порівняно з регулярним виразом, це просто інший варіант. Це надмірно для справи ОП.
JohnnyHK

2
За винятком регулярних виразів, це значно повільніше. Повнотекстовий пошук також повільний, але не такий повільний. Найшвидший (але більш роздутий) спосіб буде окремим полем, яке завжди встановлюється в малі регістри.
Том Меттам

4
Використання повнотекстового пошуку неправильно в цьому випадку (і потенційно небезпечно ), тому що мова йшла про прийняття запиту до регістру, наприклад , username: 'bill'узгодження BILLабо BillНЕ пошуковий запит повного тексту, який буде також матч стебел слова з bill, наприклад, Bills, і billedт.д.
Dan Dăscălescu

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

2
Ви подивилися на відповіді, перш ніж опублікувати цю? Замість квазі-дублюючої відповіді, що стосується лише коду, ви можете пояснити, як вона додає щось цінне порівняно з попередніми відповідями.
Дан Даскалеску

1
Я просто хочу додати, що ця відповідь - це те, що наштовхнуло мене на рішення. Я використовую рамку PHP, і це добре вписується в синтаксис ORM, тоді як інші рішення тут не робили. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Дон Жешут

9

Mongo (поточна версія 2.0.0) не дозволяє здійснювати нечутливі до регістру пошуки в індексованих полях - дивіться їх документацію . Для неіндексованих полів регулярні вирази, зазначені в інших відповідях, повинні бути нормальними.


19
Просто для уточнення цього: неінтенсивні пошукові запити дозволені в індексованих полях, вони просто не використовуватимуть індекс і будуть настільки повільними, як якщо б поле не було індексовано.
Heavi5ide

@ Heavi5ide, оскільки це запитання використовується для позначення дублікатів, я думав, що уточнив би, що регулярні вирази (необхідні для нечутливих пошукових запитів) використовують індекс, однак вони повинні виконати повне сканування індексу. Іншими словами, вони не можуть ефективно використовувати індекс. На щастя, документація з тих пір оновлювалася з 2011 року, але все ж добре зазначити і тут.
Саммає

7

Одне дуже важливе, що потрібно пам’ятати при використанні запиту на основі Regex - Коли ви робите це для системи входу, уникайте кожного шуканого символу , і не забувайте операторів ^ і $. Lodash має для цього хорошу функцію , якщо ви вже використовуєте її:

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

Чому? Уявіть користувача, який вводить .*як своє ім’я користувача. Це відповідатиме всім іменам користувачів, що дозволяє ввійти, просто відгадавши пароль будь-якого користувача.


6

Найкращий метод полягає у вашій вибраній мові: під час створення модельної обгортки для ваших об'єктів переконайте, що ваш метод збереження () повторюється через набір полів, за якими ви будете шукати, які також індексуються; цей набір полів повинен мати рядкові аналоги, які потім використовуються для пошуку.

Кожен раз, коли об’єкт зберігається знову, властивості нижнього регістру потім перевіряються та оновлюються з будь-якими змінами основних властивостей. Це дозволить вам ефективно шукати, але приховувати зайві роботи, необхідні для оновлення полів ПК щоразу.

Поле в нижньому регістрі може бути ключовим: зберігати об'єкт значення або просто ім'я поля з префіксом lc_. Я використовую другий для спрощення запитів (глибокий запит об'єкта часом може бути заплутаним).

Примітка: ви хочете проіндексувати поля lc_, а не основні поля, на яких вони базуються.


Приємне рішення, але, на щастя, починаючи з MongoDB 3.4, є вбудована підтримка індексів чутливих до справи .
Дан Даскалеску

6

Припустимо, ви хочете шукати "стовпець" у "Таблиці", і ви хочете беззахисний пошук. Найкращий і ефективний спосіб, як показано нижче;

//create empty JSON Object
mycolumn = {};

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

Вищевказаний код просто додає ваше значення пошуку як RegEx та здійснює пошук за критеріями нечутливості, встановленими як "i" як варіант.

Все найкраще.


5

Використовуючи Mongoose, це працювало для мене:

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

8
Чи не є .toLowerCase()зайвим, якщо ви вказуєте прапор, нечутливий до регістру i?
k00k

Так. Вам не потрібно .toLowerCase (). Я її зняв із відповіді.
Крісріч

хм, це має працювати так? Коли я шукаю "позначку", він також отримує кожен запис із "marko" - чи є спосіб ігнорувати лише регістр?
Suisse

Гаразд знайшов, правильним регулярним виразом буде: '^' + serach_name + '$', "i"
Suisse

3
Це НЕБЕЗПЕЧНО. Ви не уникаєте імені користувача, тому будь-який довільний регулярний вираз може бути введений.
Том Меттам

3

Рамка агрегації була введена в mongodb 2.2. Ви можете скористатися рядковим оператором "$ strcasecmp", щоб зробити порівняння між рядками нечутливим до регістру. Це більш рекомендовано і простіше, ніж використовувати регулярний гекс.

Ось офіційний документ про оператора команд агрегації: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .


4
як використовувати це у запиті find ()? db.stuff.find ({ім'я: $ strcasecmp (ім'я)})?
Suisse

3

Ви можете використовувати індекси чутливості випадків :

Наступний приклад створює колекцію без порівняння за замовчуванням, після чого додає індекс у поле імені з нечутливим порівнянням регістру. Міжнародні компоненти для 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 } } )

Для використання індексу запити повинні вказувати те саме порівняння.

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

або ви можете створити колекцію із зіставленням за замовчуванням:

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

Здається, незначна синтаксична проблема (відсутні дужки). Оновіть запит: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal

3

Для пошуку змінної та виходу з неї:

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

Уникнення змінної захищає запит від атак з '. *' Або іншим регулярним виразом.

escape-string-regexp


1

Використовуйте RegExp. У випадку, якщо будь-які інші параметри не працюють для вас, RegExp є хорошим варіантом. Це робить корпус струни нечутливим.

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

використовувати ім'я користувача у запитах, а потім його зробити.

Я сподіваюся, що це теж спрацює і для вас. Всього найкращого.


0

Я створив простий Func для регістру нечутливого до випадку, який я використовую у своєму фільтрі.

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

Потім ви просто фільтруєте на полі наступним чином.

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

0

Використання фільтра працює для мене в 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();

Він може навіть використовувати індекс, тому що я вважаю, що методи викликаються після повернення, але я ще не перевіряв цього.

Це також дозволяє уникнути проблеми

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

що mongodb подумає, що p.Title.ToLower () є властивістю і не відобразиться належним чином.


Дякую, це працює для мене. Тут нам потрібно отримати фільтр у змінній, а потім передати метод Find ().
Нілай


-1

Як ви бачите в документах mongo - оскільки $textіндекс версії 3.2 за замовчуванням нечутливий до регістру: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensibility

Створіть текстовий покажчик та використовуйте у своєму запиті текстовий оператор $ .


Використання повнотекстового пошуку неправильно в цьому випадку (і потенційно небезпечно ), тому що мова йшла про прийняття запиту до регістру, наприклад , username: 'bill'узгодження BILLабо BillНЕ пошуковий запит повного тексту, який буде також матч стебел слова з bill, наприклад, Billsта billedін.
Дан Даскалеску

-1

Вони перевірені на пошук рядків

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

Я зіткнувся з подібною проблемою, і ось що для мене спрацювало:

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

Це рішення вже давали двічі. Будь ласка, перевірте наявні відповіді, перш ніж публікувати нову.
Дан Даскалеску

@DanDascalescu не впевнений, про що ви говорите, на CTRL + F подібне рішення з багатьма оновленими повідомленнями було опубліковано у вересні 2018 року. Я відповів на це у квітні 2018 року. Я фактично розмістив це, оскільки його наразі немає. Будь ласка, перевірте, коли він розміщений, перш ніж попередити тих, хто просто щиро намагається допомогти.
Вуппі

Я говорю про цю відповідь з квітня 2016 року, і цю відповідь з травня 2016 року. І використання, $regexі $options. Що ти Ctrl + F?
Дан Даскалеску

Також використання $regexє неефективним та потенційно небезпечним, як я пояснив у своїй редакції до цієї іншої відповіді 2016 року . Не соромно видаляти відповіді, якщо вони більше не служать громаді!
Дан Даскалеску

Відзначено неефективним $ regex, спасибі велике. I Ctrl + F $ параметри. Тут ми лише двоє, без нового Regexp у нашому коді $ regex, квітень 2018 та вересень 2018 року. Я не використовував новий Regexp у своїй відповіді. Я забув конкретну проблему з новим Regexp, який вирішується, коли я його видалив і просто використовую це рішення, яке я розмістив замість цього.
Вуппі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.