MongoDB: Можно ли сделать запрос без учета регистра?

232

Пример:

> 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 регистра с помощью $caseSensitive: false . См .: docs.mongodb.org/manual/reference/operator/query/text/…
  • 4
    Обратите внимание, что это только для текстовых индексов.
Теги:
case-insensitive

23 ответа

273
Лучший ответ

Вы можете использовать regex.

В вашем примере это будет:

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

Я должен сказать, хотя, может быть, вы могли бы просто изменить (или высвободить) значение на пути, а не приносить дополнительную стоимость каждый раз, когда найдете его. Очевидно, что это не будет работать для имен людей и т.д., Но, возможно, для использования в виде тегов.

  • 25
    Это работает отлично. Работаем в PHP с: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
  • 2
    Особенно, если вы интерполируете строку ({foo: / # {x} / i}), в которой может быть знак вопроса ..
Показать ещё 13 комментариев
180

UPDATE:

Оригинальный ответ теперь устарел. Mongodb теперь поддерживает расширенный полнотекстовый поиск со многими функциями.

ОРИГИНАЛЬНЫЙ ОТВЕТ:

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

Даже с небольшими наборами данных он не очень эффективен. Вы получаете гораздо больший хит процессора, чем ваши ордера на запрос, что может стать проблемой, если вы пытаетесь достичь масштаба.

В качестве альтернативы вы можете сохранить заглавную копию и выполнить поиск по ней. Например, у меня есть таблица User, у которой есть имя пользователя, которое является смешанным случаем, но идентификатор является заглавной копией имени пользователя. Это гарантирует, что дублирование с учетом регистра невозможно (наличие "Foo" и "foo" не будет разрешено), и я могу выполнить поиск по id = username.toUpperCase(), чтобы получить поиск по имени пользователя без учета регистра.

Если ваше поле большое, например тело сообщения, дублирование данных, вероятно, не является хорошим вариантом. Я считаю, что использование альтернативного индексатора, такого как Apache Lucene, является лучшим вариантом в этом случае.

  • 0
    Есть ли документация, показывающая, как работают индексы? Я спрашиваю, потому что, если я не забуду, marklogic может хранить дополнительный нечувствительный к регистру индекс ... может быть, Монго делает то же самое?
  • 0
    Raymo, нечувствительная к регистру функция индекса сегодня не существует в Mongo, но об этом говорят. jira.mongodb.org/browse/SERVER-90
Показать ещё 7 комментариев
56

Если вам нужно создать regexp из переменной, это гораздо лучший способ сделать это: https://stackoverflow.com/questions/10728043/mongo-query-with-regex-in-node-js-operating-on-a-variable

Затем вы можете сделать что-то вроде:

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

Это имеет преимущество быть более программным, или вы можете получить повышение производительности путем компиляции его заранее, если вы многократно используете его.

  • 0
    new RegExp("^" + req.params.term.toLowerCase(), "i") также работает отлично
  • 1
    вам следует рассмотреть возможность экранирования строки для повышения безопасности, если переменная приходит из запроса: stackoverflow.com/a/50633536/5195127
56

Имейте в виду, что предыдущий пример:

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

приведет к тому, что каждая запись, содержащая бар, будет соответствовать запросу (bar1, barxyz, openbar), это может быть очень опасно для поиска имени пользователя в функции auth...

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

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

См. http://www.regular-expressions.info/ для справки по синтаксису для регулярных выражений

22

Начиная с Mongodb 3.4, вы должны использовать индекс сортировки без учета регистра. Это самый быстрый способ выполнить поиск без учета регистра по наборам данных все большего размера. Я лично написал одному из основателей, чтобы он работал, и он сделал это! (Это была проблема в JIRA около 5 лет, и многие просили эту функцию). Вот как это работает:

Индекс без учета регистра создается путем указания параметров сортировки с силой 1 или 2. Вы можете создать индекс без учета регистра, например:

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

Или вы можете сделать это для всей коллекции по умолчанию при создании базы данных следующим образом:

db.createCollection("Cities",{collation: {locale: "en",strength:2}});

И используйте это так:

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

Это вернет "Нью-Йорк", "Нью-Йорк" и т.д.

В качестве альтернативы вы можете заставить все индексы использовать параметры сортировки по умолчанию при создании коллекции следующим образом:

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

Преимущество этого метода заключается в значительном улучшении эффективности и скорости в больших наборах данных.

Для получения дополнительной информации: https://jira.mongodb.org/browse/SERVER-90, https://docs.mongodb.com/manual/reference/collation/

  • 0
    Важным моментом является то, что «Для использования индекса запросы должны указывать одно и то же сопоставление», в явном виде: docs.mongodb.com/manual/core/index-case-insensitive/…
16
db.zipcodes.find({city : "NEW YORK"}); // Case-sensitive
db.zipcodes.find({city : /NEW york/i}); // Note the 'i' flag for case-insensitivity
  • 1
    Пожалуйста, добавьте описание к коду.
  • 6
    @ParthTrivedi, комментарии примерно столько же, сколько сам код. Вы хотите эссе на 3 страницы или что-то?
Показать ещё 1 комментарий
12

TL; DR

Правильный способ сделать это в mongo

Не используйте RegExp

Go natural И использовать встроенную индексацию 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:

Необходимо создать индекс в зависимости от того, какое ТЕКСТ поле, которое вы хотите искать, без запроса индексирования будет чрезвычайно медленным

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
    Хороший вариант, но нет ничего более «правильного» в использовании текстового индекса по сравнению с регулярным выражением, это просто другой вариант. Это слишком для дела ОП.
  • 1
    Кроме того, регулярное выражение значительно медленнее. Полнотекстовый поиск также медленный, но не такой медленный. Самый быстрый (но более раздутый) путь - это отдельное поле, которое всегда устанавливается в нижний регистр.
9

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

  • 18
    Просто чтобы прояснить это: поиск без учета регистра допускается в индексированных полях, они просто не будут использовать индекс и будут такими же медленными, как если бы поле не было проиндексировано.
  • 0
    @ heavyi5ide, поскольку этот вопрос используется для отметки дубликатов. Я подумал, что хотел бы уточнить, что регулярные выражения (необходимые для поиска без учета регистра) действительно используют индекс, однако они должны выполнить полное сканирование индекса. Другими словами, они не могут эффективно использовать индекс. К счастью, документация была обновлена с 2011 года, но все же приятно отметить и здесь.
7

Используя 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 без .toLowerCase() регистра?
  • 0
    Да, это. Вам не нужно .toLowerCase (). Я удалил это из ответа.
Показать ещё 3 комментария
5

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

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

Почему? Представьте, что пользователь вводит .* в качестве своего имени пользователя. Это будет соответствовать всем именам пользователей, что позволит войти в систему, просто угадывая пароль пользователя.

5

Предположим, что вы хотите найти "столбец" в "Таблице", и вы хотите, чтобы поиск в insensstive. Лучший и эффективный способ, как показано ниже:

//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 и выполняется поиск с использованием критериев insensitve, установленных с опцией "i".

Все самое лучшее.

5

Лучший способ заключается в выборе вашего языка при создании обертки модели для ваших объектов, попробуйте выполнить метод save() через набор полей, которые вы будете искать, которые также индексируются; эти группы полей должны иметь строчные копии, которые затем используются для поиска.

Каждый раз, когда объект снова сохраняется, свойства нижнего регистра затем проверяются и обновляются с любыми изменениями основных свойств. Это позволит вам эффективно искать, но скрыть дополнительную работу, необходимую для обновления полей lc каждый раз.

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

Примечание: вы хотите индексировать поля lc_, а не основные поля, на которых они основаны.

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

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

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

  • 3
    как использовать это в запросе find ()? db.stuff.find ({имя: $ strcasecmp (имя)})?
1

Для поиска и экранирования переменной:

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

Выход из переменной защищает запрос от атак с помощью '. *' Или другого регулярного выражения.

бежать строка-регулярное выражение

1

Вы можете использовать Нечувствительные к регистру индексы:

В следующем примере создается коллекция без сортировки по умолчанию, а затем добавляется индекс в поле имени с учетом нечувствительности к регистру. Международные компоненты для Юникода

/* 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
  • 0
    Кажется, есть небольшая проблема с синтаксисом (отсутствуют скобки). Пожалуйста, обновите запрос: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
1

Использование фильтра работает для меня на С#.

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() является свойством и не будет правильно отображаться.

  • 0
    Спасибо, это работает для меня. Здесь нам нужно получить фильтр в переменной, а затем передать метод Find ().
0

Для любого, кто использует Golang и хочет иметь полнотекстовый поиск с учетом регистра с помощью mongodb и библиотеки globalsign mgo godoc.

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


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

Используйте RegExp, в случае, если какие-либо другие варианты не работают для вас, RegExp является хорошим вариантом. Это делает строку чувствительной к регистру.

var username = "John";

var uname = new RegExp(username, "i");

Значение uname будет похоже на /John/i.

используйте uname в запросах вместо имени пользователя, и тогда все готово.

Я надеюсь, что это сработает и для вас. Всего наилучшего.

0

Я столкнулся с подобной проблемой, и это то, что работает для меня:

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

Они были протестированы для поиска строк

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

Как вы можете видеть в mongo docs - поскольку версия 3.2 $text по умолчанию не учитывает регистр: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Создать текстовый индекс и использовать $text operator в вашем запрос.

0

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

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

Затем вы просто фильтруете поле в следующем виде.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню