MongoDB: Возможно ли сделать запрос без учета регистра?
Пример:
> db.stuff.save({"foo":"bar"});
> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0
27 ответов
Вы могли бы использовать регулярное выражение.
В вашем примере это будет:
db.stuff.find( { foo: /^bar$/i } );
Я должен сказать, однако, возможно, вы могли бы просто уменьшить (или увеличить) значение на пути, а не нести дополнительные расходы каждый раз, когда вы их найдете. Очевидно, что это не сработает для имен людей и тому подобного, но, может быть, они используются в качестве тегов.
ОБНОВИТЬ:
Первоначальный ответ устарел. Mongodb теперь поддерживает расширенный полнотекстовый поиск со многими функциями.
ОРИГИНАЛЬНЫЙ ОТВЕТ:
Следует отметить, что поиск с регистронезависимым регулярным выражением / i означает, что mongodb не может выполнять поиск по индексу, поэтому запросы к большим наборам данных могут занимать много времени.
Даже с небольшими наборами данных это не очень эффективно. Вы получаете гораздо больший удар по процессору, чем ваш запрос, что может стать проблемой, если вы пытаетесь достичь масштаба.
В качестве альтернативы вы можете сохранить заглавную копию и выполнить поиск по ней. Например, у меня есть таблица User с именем пользователя в смешанном регистре, но id является копией имени пользователя в верхнем регистре. Это гарантирует, что дублирование с учетом регистра невозможно (наличие "Foo" и "foo" не допускается), и я могу выполнить поиск по id = username.toUpperCase(), чтобы получить поиск имени пользователя без учета регистра.
Если у вас большое поле, такое как тело сообщения, дублирование данных, вероятно, не очень хороший вариант. Я считаю, что использование постороннего индексатора, такого как Apache Lucene, является лучшим вариантом в этом случае.
Начиная с 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/
Если вам нужно создать регулярное выражение из переменной, это гораздо лучший способ сделать это: /questions/45906098/mongo-query-s-regex-v-node-js-rabotayuschem-po-peremennomu/45906119#45906119
Затем вы можете сделать что-то вроде:
var string = "SomeStringToFind";
var regex = new RegExp(["^", string, "$"].join(""), "i");
// Creates a regex of: /^SomeStringToFind$/i
db.stuff.find( { foo: regex } );
Преимущество в том, что вы будете более программируемыми, или вы сможете получить повышение производительности, заблаговременно скомпилировав его, если будете многократно его использовать.
Имейте в виду, что предыдущий пример:
db.stuff.find( { foo: /bar/i } );
приведет к тому, что все записи, содержащие bar, будут соответствовать запросу ( bar1, barxyz, openbar), это может быть очень опасно для поиска имени пользователя в функции аутентификации...
Вам может понадобиться, чтобы он соответствовал только поисковому запросу, используя соответствующий синтаксис регулярного выражения, например:
db.stuff.find( { foo: /^bar$/i } );
См. http://www.regular-expressions.info/ для получения справки по синтаксису регулярных выражений.
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});
db.zipcodes.find({city : "NEW YORK"}); // Case-sensitive
db.zipcodes.find({city : /NEW york/i}); // Note the 'i' flag for case-insensitivity
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
При использовании запроса на основе Regex следует помнить одну очень важную вещь: когда вы делаете это для системы входа в систему, избегайте каждого отдельного символа, который вы ищете, и не забывайте операторы ^ и $. У Lodash есть хорошая функция для этого, если вы уже используете ее:
db.stuff.find({$regex: new RegExp(_.escapeRegExp(bar), $options: 'i'})
Зачем? Представьте, что пользователь входит .*
как его имя пользователя. Это будет соответствовать всем именам пользователей, позволяя войти в систему, просто угадав пароль любого пользователя.
Для поиска и экранирования переменной:
const escapeStringRegexp = require('escape-string-regexp')
const name = 'foo'
db.stuff.find({name: new RegExp('^' + escapeStringRegexp(name) + '$', 'i')})
Выход из переменной защищает запрос от атак с помощью '.*' Или другого регулярного выражения.
Предположим, вы хотите выполнить поиск по "столбцу" в "Таблице" и хотите выполнить поиск без учета регистра. Лучший и эффективный способ, как показано ниже;
//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".
Всего наилучшего.
Используя Mongoose это сработало для меня:
var find = function(username, next){
User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
if(err) throw err;
next(null, res);
});
}
Mongo (текущая версия 2.0.0) не позволяет выполнять поиск по индексированным полям без учета регистра - см. Их документацию. Для неиндексированных полей регулярные выражения, перечисленные в других ответах, должны подойти.
Наилучший метод на выбранном вами языке: при создании обёртки модели для ваших объектов, пусть ваш метод save() выполняет итерацию по набору полей, которые вы будете искать, и которые также проиндексированы; этот набор полей должен иметь строчные буквы, которые затем используются для поиска.
Каждый раз, когда объект сохраняется снова, свойства нижнего регистра проверяются и обновляются с учетом любых изменений основных свойств. Это позволит вам эффективно выполнять поиск, но при этом каждый раз будет скрывать дополнительную работу, необходимую для обновления полей lc.
Поля в нижнем регистре могут быть хранилищем объектов ключ: значение или просто именем поля с префиксом lc_. Я использую второй для упрощения запросов (глубокие запросы к объектам могут иногда сбивать с толку).
Примечание: вы хотите индексировать поля lc_, а не основные поля, на которых они основаны.
Если вы используете MongoDB Compass:
Зайдите в коллекцию, в типе фильтра -> {Fieldname: / string / i}
Для Node.js с использованием Mongoose:
Model.find({Имя поля: {$regex: "stringToSearch", $options: "i"}})
Вы можете использовать регистры без учета регистра:
В следующем примере создается коллекция без сопоставления по умолчанию, затем добавляется индекс в поле имени с сопоставлением без учета регистра. Международные компоненты для 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
Структура агрегации была введена в mongodb 2.2 . Вы можете использовать строковый оператор "$strcasecmp" для сравнения строк без учета регистра. Это более рекомендуется и проще, чем использование регулярных выражений.
Вот официальный документ об операторе команды агрегации: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/.
Я удивлен, что никто не предупредил о риске инъекции регулярного выражения при использовании
/^bar$/i
если бар - это пароль или поиск по идентификатору учетной записи. (Т.е.
bar => .*@myhackeddomain.com
например, вот и моя ставка: используйте
\Q
\E
специальные символы регулярных выражений! предоставлено в PERL
db.stuff.find( { foo: /^\Qbar\E$/i } );
Вы должны экранировать переменную
бара\
символы с
\\
избежать
\E
использовать снова, когда, например,
bar = '\E.*@myhackeddomain.com\Q'
Другой вариант - использовать стратегию escape-символа регулярного выражения, подобную описанной здесь. Эквивалент Javascript Perl \Q ... \E или quotemeta()
Если в запросе есть специальные символы, простое регулярное выражение не сработает. Вам нужно будет избежать этих специальных символов.
Следующая вспомогательная функция может помочь без установки какой-либо сторонней библиотеки:
const escapeSpecialChars = (str) => {
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
И ваш запрос будет таким:
db.collection.find({ field: { $regex: escapeSpecialChars(query), $options: "i" }})
Надеюсь, это поможет!
Да, это возможно
Вы можете использовать $expr следующим образом:
$expr: {
$eq: [
{ $toLower: '$STRUNG_KEY' },
{ $toLower: 'VALUE' }
]
}
Пожалуйста, не используйте регулярное выражение, потому что это может вызвать много проблем, особенно если вы используете строку, пришедшую от конечного пользователя.
Используйте RegExp, в случае, если какие-либо другие варианты не работают для вас, RegExp является хорошим вариантом. Это делает строку чувствительной к регистру.
var username = "John";
var uname = new RegExp(username, "i");
Значение uname
будет как /John/i
,
используйте uname в запросах вместо имени пользователя, и тогда все готово.
Я надеюсь, что это сработает и для вас. Всего наилучшего.
Использование фильтра работает для меня в 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() является свойством и не будет отображаться правильно.
Я столкнулся с подобной проблемой, и это то, что работает для меня:
const flavorExists = await Flavors.findOne({
'flavor.name': { $regex: flavorName, $options: 'i' },
});
Я создал простой Func для регулярного выражения без учета регистра, который я использую в своем фильтре.
private Func<string, BsonRegularExpression> CaseInsensitiveCompare = (field) =>
BsonRegularExpression.Create(new Regex(field, RegexOptions.IgnoreCase));
Затем вы просто фильтруете поле следующим образом.
db.stuff.find({"foo": CaseInsensitiveCompare("bar")}).count();
Для любого, кто использует Golang и хочет иметь полнотекстовый поиск с учетом регистра с помощью mongodb и библиотеки globalsign mgo godoc.
collation := &mgo.Collation{
Locale: "en",
Strength: 2,
}
err := collection.Find(query).Collation(collation)
Они были проверены на поиск строк
{'_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
Как вы можете видеть в документах Монго - начиная с версии 3.2 $text
по умолчанию индекс не учитывает регистр: https://docs.mongodb.com/manual/core/index-text/
Создайте текстовый индекс и используйте оператор $text в своем запросе.