MongoDB:是不是可以进行不区分大小写的查询?
Posted
技术标签:
【中文标题】MongoDB:是不是可以进行不区分大小写的查询?【英文标题】:MongoDB: Is it possible to make a case-insensitive query?MongoDB:是否可以进行不区分大小写的查询? 【发布时间】:2010-12-24 05:15:43 【问题描述】:例子:
> db.stuff.save("foo":"bar");
> db.stuff.find("foo":"bar").count();
1
> db.stuff.find("foo":"BAR").count();
0
【问题讨论】:
从 MongoDB 3.2 开始,您可以使用$caseSensitive: false
执行不区分大小写的搜索。见:docs.mongodb.org/manual/reference/operator/query/text/…
请注意,这仅适用于文本索引。
@martin: $caseSensitive
默认情况下已经是假的,这并不能回答问题,因为它只适用于索引字段。 OP 正在寻找不区分大小写的字符串比较。
查找重复项的最佳选择:***.com/questions/40978162/…
【参考方案1】:
如果您使用的是 MongoDB Compass:
转到集合,在过滤器类型中 -> Fieldname: /string/i
对于使用 Mongoose 的 Node.js:
Model.find(FieldName: $regex: "stringToSearch", $options: "i")
【讨论】:
【参考方案2】:假设您想在“表格”中搜索“列”,并且您需要不区分大小写的搜索。最好和最有效的方法是:
//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”作为选项设置的不敏感条件进行搜索。
【讨论】:
【参考方案3】:我很惊讶如果 bar 是密码或帐户 ID 搜索,没有人警告过使用 /^bar$/i
进行正则表达式注入的风险。 (例如,bar => .*@myhackeddomain.com
,所以我敢打赌:使用\Q
\E
正则表达式特殊字符! PERL 中提供
db.stuff.find( foo: /^\Qbar\E$/i );
您应该使用\\
转义bar 变量\
字符以避免\E
再次被利用,例如bar = '\E.*@myhackeddomain.com\Q'
另一种选择是使用正则表达式转义字符策略,就像这里描述的那样javascript equivalent of Perl's \Q ... \E or quotemeta()
【讨论】:
【参考方案4】:从 MongoDB 3.4 开始,执行不区分大小写的快速搜索的推荐方法是使用 Case Insensitive Index。
我亲自向其中一位创始人发送了电子邮件,请他完成这项工作,他成功了!这是一个issue on JIRA since 2009,许多人都要求使用该功能。以下是它的工作原理:
通过指定强度为 1 或 2 的 collation 来创建不区分大小写的索引。您可以像这样创建不区分大小写的索引:
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
);
这将返回“New York”、“new york”、“New york”等。
其他说明
在这种情况下,建议使用全文搜索的答案是错误的(并且可能危险)。问题是关于进行不区分大小写的查询,例如username: 'bill'
匹配BILL
或Bill
,不是全文搜索查询,也匹配bill
的stemmed字词,如Bills
、billed
等。
建议使用正则表达式的答案很慢,因为即使有索引,documentation states:
“不区分大小写的正则表达式查询通常无法有效地使用索引。$regex 实现不支持排序规则,无法使用不区分大小写的索引。”
$regex
的回答也会冒user input injection 的风险。
【讨论】:
对我来说效果很好,即使是聚合管道。 我认为这是正确的答案,因为数据读取速度很重要 我似乎找不到任何方法可以在创建集合后将默认排序规则添加到集合中。有什么办法吗?【参考方案5】:使用 RegExp, 如果任何其他选项不适合您,RegExp 是一个不错的选择。它使字符串不区分大小写。
var username = new RegExp("^" + "John" + "$", "i");;
在查询中使用用户名,然后就完成了。
我希望它也对你有用。万事如意。
【讨论】:
【参考方案6】:对于任何使用 Golang 并希望使用 mongodb 和 mgo godoc globalsign library 进行区分大小写的全文搜索的人。
collation := &mgo.Collation
Locale: "en",
Strength: 2,
err := collection.Find(query).Collation(collation)
【讨论】:
【参考方案7】:db.company_profile.find( "companyName" : "$regex" : "Nilesh" , "$options" : "i");
【讨论】:
在发布此答案之前,您是否查看过现有答案?您可能想解释一下与以前的答案相比,它如何增加一些价值,而不是准重复的纯代码答案。 我只想补充一点,这个答案让我找到了解决方案。我正在使用 php 框架,它非常适合 ORM 语法,而这里的其他解决方案则不适合。$existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
只想重申,像这样使用 $regex 会导致集合扫描,尤其是当您使用“^...$”时。完整的解释可以在 Mongo 链接here 上找到。随着您的收藏增加,性能会受到显着影响。【参考方案8】:
用于搜索变量并将其转义:
const escapeStringRegexp = require('escape-string-regexp')
const name = 'foo'
db.stuff.find(name: new RegExp('^' + escapeStringRegexp(name) + '$', 'i'))
转义变量可以保护查询免受“.*”或其他正则表达式的攻击。
escape-string-regexp
【讨论】:
escapeStringRegexp 真是一个有用的评论【参考方案9】:我遇到过类似的问题,这对我有用:
const flavorExists = await Flavors.findOne(
'flavor.name': $regex: flavorName, $options: 'i' ,
);
【讨论】:
这个解决方案之前已经给出了两次。请在发布新答案之前检查现有答案。 @DanDascalescu 不确定您在说什么,在 CTRL+F 上,类似的解决方案有很多赞成票于 2018 年 9 月发布。我在 2018 年 4 月发布了我的答案。我实际上发布了这个,因为没有当时。在警告那些真正试图提供帮助的人之前,请检查它的发布时间。 我说的是 2016 年 4 月的 this answer 和 2016 年 5 月的 this answer。两者都使用$regex
和 $options
。你按什么Ctrl+F?
另外,正如我在对this other 2016 answer 的编辑中解释的那样,使用$regex
效率低下并且可能不安全。如果答案不再为社区服务,删除答案并不丢人!
注意到 $regex 效率低下,非常感谢。我 Ctrl+F $选项。我们只有两个在我们的 $regex 代码中没有新的正则表达式,2018 年 4 月和 2018 年 9 月。我没有在我的答案中使用新的正则表达式。我忘记了新 Regexp 的具体问题,当我删除它时解决了它,只使用我发布的这个解决方案。【参考方案10】:
您可以使用不区分大小写的索引:
以下示例创建一个没有默认排序规则的集合,然后使用不区分大小写的排序规则在名称字段上添加索引。 International Components for 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 )
【参考方案11】:
在 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() 方法。 漂亮而简单。泰!【参考方案12】:聚合框架是在 mongodb 2.2 中引入的。您可以使用字符串运算符“$strcasecmp”在字符串之间进行不区分大小写的比较。它比使用正则表达式更推荐和更容易。
这里是关于聚合命令操作符的官方文档:https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp。
【讨论】:
如何在 find() 查询中使用它? db.stuff.find( name: $strcasecmp(name)) ?【参考方案13】:使用猫鼬这对我有用:
var find = function(username, next)
User.find('username': $regex: new RegExp('^' + username, 'i'), function(err, res)
if(err) throw err;
next(null, res);
);
【讨论】:
如果您指定i
的不区分大小写标志,.toLowerCase()
不是多余的吗?
是的。您不需要 .toLowerCase()。我已将其从答案中删除。
嗯这应该像那样工作吗?当我搜索“mark”时,它还会获取带有“marko”的每条记录 - 有没有办法只忽略区分大小写?
好的,找到了,正确的正则表达式是:'^' + serach_name + '$', "i"
这是危险的。您没有转义用户名,因此可以注入任意正则表达式。【参考方案14】:
这些已经过字符串搜索测试
'_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
【讨论】:
对搜索查询非常有用的正则表达式。【参考方案15】:正如您在 mongo 文档中看到的 - 因为版本 3.2 $text
默认情况下索引不区分大小写:https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity
Create a text index 和 use $text operator in your query。
【讨论】:
在这种情况下使用全文搜索是错误的(并且可能危险),因为问题是关于进行不区分大小写的查询,例如username: 'bill'
匹配BILL
或Bill
,不是全文搜索查询,也会匹配bill
中的stemmed words,如Bills
、billed
等。【参考方案16】:
您可以使用regex。
在你的例子中是:
db.stuff.find( foo: /^bar$/i );
不过,我必须说,也许您可以在途中降低(或提高)价值,而不是每次找到它时都会产生额外的成本。显然,这不适用于人名等,但可能适用于标签等用例。
【讨论】:
这非常有效。让它在 PHP 中工作: $collection->find(array('key' => new MongoRegex('/'.$val.'/i'))); 特别是如果您要插入一个可能带有问号的字符串 (foo: /#x/i)。 别忘了还有 ^ 和 $ : MongoRegex('/^' . preg_quote($val) . '$/i') 请注意,这将执行全扫描而不是使用索引。 如果他在开头使用 ^ 锚,它不会进行全扫描,因此 Julien 的建议很重要。【参考方案17】:更新:
原来的答案现在已经过时了。 Mongodb now supports advanced full text searching, with many features.
原始答案:
需要注意的是,使用正则表达式不区分大小写的 /i 进行搜索意味着 mongodb 无法按索引进行搜索,因此针对大型数据集的查询可能需要很长时间。
即使是小型数据集,它也不是很有效。您的 CPU 命中率远高于查询保证,如果您试图实现规模化,这可能会成为一个问题。
作为替代方案,您可以存储大写副本并针对该副本进行搜索。例如,我有一个用户表,其中包含混合大小写的用户名,但 id 是用户名的大写副本。这确保了区分大小写的重复是不可能的(不允许同时使用“Foo”和“foo”),并且我可以通过 id = username.toUpperCase() 进行搜索以获得对用户名的不区分大小写的搜索。
如果您的字段很大,例如消息正文,复制数据可能不是一个好的选择。我相信在这种情况下使用像 Apache Lucene 这样的无关索引器是最好的选择。
【讨论】:
我最近用 mongodb 3.0.4 测试了 100,000 条记录,并索引了一个名称字段。不区分大小写的正则表达式查询需要超过 200 毫秒,而区分大小写的正则表达式需要大约 16 毫秒。(两种情况都包括以 '^' 开头的正则表达式) 文档可能已更新。他们现在说“对于区分大小写的正则表达式查询,如果字段存在索引,那么 MongoDB 会将正则表达式与索引中的值进行匹配,这可能比集合扫描更快。” 文本索引的另一个限制是每个集合只能有一个(多列),因此如果您需要针对不同情况隔离不同字段的搜索,则不适合。 @SergiySokolenko:文档现在说(the section 中的最后一段):“不区分大小写的正则表达式查询通常不能有效地使用索引。$regex 实现不支持排序规则并且无法利用不区分大小写的索引。” 在这种情况下使用全文搜索是错误的(并且可能危险),因为问题是关于进行不区分大小写的查询,例如username: 'bill'
匹配BILL
或Bill
,不是全文搜索查询,也匹配bill
中的stemmed words,如Bills
、billed
等。【参考方案18】:
TL;DR
在 mongo 中执行此操作的正确方法
不要使用 RegExp
Go natural And use mongodb's inbuilt indexing , search
第 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
]
)
第二步:
需要在要搜索的TEXT字段上创建索引,没有索引查询会非常慢
db.articles.createIndex( subject: "text" )
第三步:
db.articles.find( $text: $search: "coffee",$caseSensitive :true ) //FOR SENSITIVITY
db.articles.find( $text: $search: "coffee",$caseSensitive :false ) //FOR INSENSITIVITY
【讨论】:
不错的选择,但使用文本索引与使用正则表达式没有什么更“正确”的了,它只是另一种选择。对于 OP 的情况来说,这太过分了。 除了正则表达式明显慢。全文搜索也很慢,但没有那么慢。最快(但更臃肿)的方式是一个单独的字段,它总是设置为小写。 在这种情况下使用全文搜索是错误的(并且可能危险),因为问题是关于进行不区分大小写的查询,例如username: 'bill'
匹配BILL
或Bill
,不是全文搜索查询,也会匹配bill
中的stemmed words,如Bills
、billed
等。【参考方案19】:
在使用基于正则表达式的查询时要牢记一件非常重要的事情 - 当您为登录系统执行此操作时,您正在搜索 escape every single character,并且不要忘记 ^ 和 $ 运算符。 Lodash has a nice function for this,您是否已经在使用它:
db.stuff.find($regex: new RegExp(_.escapeRegExp(bar), $options: 'i')
为什么?想象一个用户输入.*
作为他的用户名。这将匹配所有用户名,只需猜测任何用户的密码即可登录。
【讨论】:
【参考方案20】:db.zipcodes.find(city : "NEW YORK"); // Case-sensitive
db.zipcodes.find(city : /NEW york/i); // Note the 'i' flag for case-insensitivity
【讨论】:
@OlegV.Volkov 必须描述您的答案如何恰当以及提问者代码中有什么问题。 这个仅代码的答案不会对 6 年前发布的已接受答案添加任何内容。【参考方案21】:我为不区分大小写的正则表达式创建了一个简单的 Func,我在过滤器中使用了它。
private Func<string, BsonRegularExpression> CaseInsensitiveCompare = (field) =>
BsonRegularExpression.Create(new Regex(field, RegexOptions.IgnoreCase));
然后您只需按如下方式过滤字段。
db.stuff.find("foo": CaseInsensitiveCompare("bar")).count();
【讨论】:
【参考方案22】:如果您需要从变量创建正则表达式,这是一种更好的方法:https://***.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 );
这样做的好处是更加程序化,或者如果您经常重复使用它,则可以通过提前编译来提高性能。
【讨论】:
new RegExp("^" + req.params.term.toLowerCase(), "i")
也可以正常工作
如果变量来自请求,您应该考虑转义字符串以提高安全性:***.com/a/50633536/5195127
从 MongoDB 3.4 开始,原生支持 Case Insensitive Indexes【参考方案23】:
Mongo(当前版本 2.0.0)不允许对索引字段进行不区分大小写的搜索 - 请参阅 their documentation。对于非索引字段,其他答案中列出的正则表达式应该没问题。
【讨论】:
只是为了澄清这一点:允许在索引字段上进行不区分大小写的搜索,它们只是不会使用索引,并且会像不使用该字段一样慢索引。 @heavi5ide 因为这个问题被用来标记重复我想我会澄清一下正则表达式(需要不区分大小写的搜索)确实使用索引,但是,它们必须进行完整的索引扫描。换句话说,他们不能有效地使用索引。幸运的是,该文档自 2011 年起已更新,但在这里也值得一提。【参考方案24】:记住前面的例子:
db.stuff.find( foo: /bar/i );
将导致包含 bar 的每个条目都匹配查询( bar1、barxyz、openbar ),这对于在 auth 函数上搜索用户名可能非常危险...
您可能需要使用适当的正则表达式语法使其仅匹配搜索词:
db.stuff.find( foo: /^bar$/i );
查看http://www.regular-expressions.info/获取正则表达式的语法帮助
【讨论】:
这个答案看起来像评论。【参考方案25】:最好的方法是使用您选择的语言,在为您的对象创建模型包装器时,让您的 save() 方法遍历一组您将要搜索的字段,这些字段也会被索引;这些字段集应具有小写对应项,然后用于搜索。
每次再次保存对象时,都会检查小写属性,并根据对主要属性的任何更改进行更新。这将使您可以高效搜索,但隐藏每次更新 lc 字段所需的额外工作。
小写字段可以是 key:value 对象存储或只是带有前缀 lc_ 的字段名称。我使用第二个来简化查询(深度对象查询有时会令人困惑)。
注意:您要索引 lc_ 字段,而不是它们所基于的主要字段。
【讨论】:
不错的解决方案,但幸运的是从 MongoDB 3.4 开始,原生支持 Case Insensitive Indexes。以上是关于MongoDB:是不是可以进行不区分大小写的查询?的主要内容,如果未能解决你的问题,请参考以下文章