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: $caseSensitiveпо умолчанию уже false, и это не отвечает на вопрос, потому что он работает только с индексированными полями. OP искал сравнение строк без учета регистра.
Дан Даскалеску

Ответы:

343

Вы могли бы использовать регулярное выражение .

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

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

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

rfunduk
источник
27
Это работает отлично. Работает в PHP с: $ collection-> find (array ('key' => new MongoRegex ('/'.$ val.' / I ')));
Люк Деннис
2
Особенно, если вы интерполируете строку ({foo: / # {x} / i}), в которой может быть знак вопроса ..
Питер Эрлих
17
Не забудьте также ^ и $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Жюльен
20
Обратите внимание, что это будет делать полное сканирование вместо использования индекса.
Мартин Коничек
12
он не будет выполнять полное сканирование, если он использует якорь в начале, отсюда и важность совета Жюльена.
Пакс
198

ОБНОВИТЬ:

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

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

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

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

В качестве альтернативы вы можете сохранить заглавную копию и выполнить поиск по ней. Например, у меня есть таблица User с именем пользователя в смешанном регистре, но id является копией имени пользователя в верхнем регистре. Это гарантирует, что дублирование с учетом регистра невозможно (наличие «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т. д.
Дан Даскалеску
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") тоже отлично работает
Таир Ясин
3
вам следует рассмотреть возможность экранирования строки для повышения безопасности, если переменная исходит из запроса: stackoverflow.com/a/50633536/5195127
davidivad
Начиная с MongoDB 3.4, существует встроенная поддержка индексов, нечувствительных к
регистру
64

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

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

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

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

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

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

jflaflamme
источник
Этот ответ выглядит как комментарий.
Дан Даскалеску
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ответы также могут привести к риску ввода данных пользователем .

user3413723
источник
Отлично сработало для меня даже с конвейером агрегации.
Морио
Я думаю, что это правильный ответ, потому что скорость чтения данных важна
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
rshivamca
источник
1
@ OlegV.Volkov должен иметь описание того, насколько уместен ваш ответ и что не так в коде спрашивающего.
Парт Триведи
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т. д.
Дан Даскалеску
15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});
Nilesh
источник
2
Вы смотрели на существующие ответы, прежде чем опубликовать этот? Вместо квазидубликативного ответа, содержащего только один код, вы можете объяснить, как он добавляет что-то ценное по сравнению с предыдущими ответами.
Дан Даскалеску
1
Я просто хочу добавить, что именно этот ответ привел меня к решению. Я использую фреймворк PHP, и это хорошо вписывается в синтаксис ORM, в то время как другие решения здесь нет. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Дон Жешут
9

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

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

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

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

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

Ник Камер
источник
6

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

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

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

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

RobKohr
источник
Хорошее решение, но, к счастью, начиная с 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);
    });
}
ChrisRich
источник
8
Разве это не .toLowerCase()избыточно, если вы указываете флаг без учета регистра i?
k00k
Да, это так. Вам не нужно .toLowerCase (). Я удалил это из ответа.
ChrisRich
хм это должно так работать? Когда я ищу «mark», он также получает каждую запись с «marko» - есть ли способ игнорировать только регистр?
Suisse
Хорошо, нашел его, правильное регулярное выражение будет: '^' + serach_name + '$', "i"
Suisse
3
Это ОПАСНО. Вы не экранируете имя пользователя, поэтому любое произвольное регулярное выражение может быть введено.
Том Меттам
3

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

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

Jogue Wasin
источник
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 } } )
Мохд Белал
3

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

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

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

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

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

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

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

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


err := collection.Find(query).Collation(collation)
okandas
источник
-1

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

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

avalanche1
источник
Использование полнотекстового поиска в этом случае является неправильным (и потенциально опасным ), потому что вопрос заключался в создании запроса без учета регистра, например, 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' },
  });
Woppi
источник
Это решение уже было дано дважды. Пожалуйста, проверьте существующие ответы, прежде чем публиковать новые.
Дан Даскалеску
@DanDascalescu не уверен, о чем вы говорите, после CTRL + F, аналогичное решение со многими отзывами опубликовало его в сентябре 2018 года. Я опубликовал свой ответ в апреле 2018 года. Я действительно опубликовал это, потому что в то время его еще не было. Пожалуйста, проверьте, когда он был опубликован, прежде чем предупреждать тех, кто просто искренне пытается помочь
Woppi
Я говорю об этом ответе с апреля 2016 года, а об этом ответе с мая 2016 года. Оба используют $regexи $options. Что ты нажал Ctrl + F?
Дан Даскалеску
Кроме того, использование $regexнеэффективно и потенциально небезопасно, как я объяснил в своем редактировании этого другого ответа 2016 года . Нет ничего постыдного в удалении ответов, если они больше не служат сообществу!
Дан Даскалеску
Отмечено на неэффективном регулярном выражении $, большое спасибо. У меня Ctrl + F $ варианты. Нас здесь только двое, и в нашем коде $ regex нет нового регулярного выражения, апрель 2018 и сентябрь 2018. Я не использовал новый регулярный выражения в своем ответе. Я забыл конкретную проблему с новым Regexp, которая была устранена, когда я удалил его, и просто использовал это решение, которое я разместил вместо этого.
Woppi