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一直在寻找不区分大小写的字符串比较。
丹·达斯卡斯库

Answers:


342

您可以使用正则表达式

在您的示例中将是:

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

不过,我必须说,也许您可​​以在输入过程中小写(或大写)该值,而不是每次找到它都会产生额外的成本。显然,这不适用于人们的姓名等,但可能适用于诸如标签之类的用例。


27
这很完美。通过以下方式在PHP中工作:$ collection-> find(array('key'=> new MongoRegex('/'.$ val。'/ i'))));
路加·丹尼斯

2
特别是如果你插串:({富/#{X} / I})可能..有一个问号在它
彼得·埃利希

17
也不要忘记^和$:MongoRegex('/ ^'。preg_quote($ val)。'$ / i')
Julien

20
请注意,这将执行全扫描而不是使用索引。
Martin Konicek 2013年

12
如果他在开始时使用^锚,则不会进行完全扫描,因此Julien的建议很重要。
Pax

198

更新:

原始答案现在已过时。Mongodb现在支持具有许多功能的高级全文搜索。

原始答案:

应该注意的是,使用正则表达式的不区分大小写的/ i进行搜索意味着mongodb无法按索引搜索,因此对大型数据集的查询可能需要很长时间。

即使数据集很小,它也不是很有效。您获得的CPU命中率比查询授权要大得多,如果您要实现规模化,这可能会成为一个问题。

或者,您可以存储大写副本并对其进行搜索。例如,我有一个User表,该表的用户名是大小写混合的,但是id是用户名的大写副本。这确保了区分大小写的复制是不可能的(不允许同时使用“ Foo”和“ foo”),并且我可以通过id = username.toUpperCase()进行搜索,以获取不区分大小写的用户名搜索。

如果您的字段很大(例如消息正文),则复制数据可能不是一个好的选择。我认为在这种情况下,使用像Apache Lucene这样的无关紧要的索引器是最好的选择。


1
@Dan,仅在最新的MongoDB中提供信息,“如果该字段存在索引,则MongoDB会将正则表达式与索引中的值进行匹配,这可能比集合扫描更快。” - docs.mongodb.org/manual/reference/operator/query/regex/...
谢尔盖Sokolenko

1
文件可能已更新。他们现在说:“对于区分大小写的正则表达式查询,如果该字段存在索引,则MongoDB将正则表达式与索引中的值进行匹配,这可能比集合扫描更快。”
杰夫·刘易斯

1
文本索引的另一个限制是每个集合只能有一个(多列),因此如果您需要针对不同的情况在不同的字段中隔离搜索,则不适合。
Paul Grimshaw

2
@SergiySokolenko:文档现在说(该节的最后一段):“不区分大小写的正则表达式查询通常不能有效地使用索引。$ regex实现不支持排序规则,并且不能使用不区分大小写的索引。”
丹·达斯卡斯库

1
使用全文搜索是错误在这种情况下(和潜在的危险),因为这个问题是关于做一个不区分大小写的查询,如username: 'bill'匹配BILLBill不完整的文本搜索查询,这也将匹配朵朵词bill,如Billsbilled等等
达恩·达斯卡莱斯卡

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

会导致每个包含bar的条目都与查询匹配(bar1,barxyz,openbar),这对于在auth函数上进行用户名搜索可能非常危险...

您可能需要使用适当的regexp语法,使其仅与搜索词匹配:

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

有关正则表达式的语法帮助,请参见http://www.regular-expressions.info/


这个答案看起来像一条评论。
丹·达斯卡斯库

62

从MongoDB 3.4开始,执行快速不区分大小写的搜索的推荐方法是使用不区分大小写的索引

我亲自给其中一位创始人发了电子邮件,请他完成这项工作,而他做到了!自2009年以来,这一直是JIRA问题,许多人都要求使用此功能。运作方式如下:

通过指定强度为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'匹配BILLBill,而不是全文搜索查询,也将匹配的词干bill,例如Billsbilled等等。
  • 建议使用正则表达式的答案很慢,因为即使使用索引,文档也指出

    “不区分大小写的正则表达式查询通常不能有效地使用索引。$ 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
@ OlegV.Volkov必须具有有关您的答案如何合适以及发问者代码中有什么问题的描述。
Parth Trivedi 2015年

1
这个仅提供代码的答案不会在6年前发布的已接受答案中添加任何内容。
Dan Dascalescu

19

TL; DR

在mongo中执行此操作的正确方法

不要使用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" } )

第三步:

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

1
不错的选择,但是使用文本索引和正则表达式没有什么“正确”的选择,这只是另一种选择。对于OP来说,这太过分了。
JohnnyHK '16

2
除了正则表达式要慢得多。全文搜索也很慢,但并不慢。最快(但更肿)的方式是将始终设置为小写的单独字段。
汤姆·梅塔姆

4
使用全文搜索是错误在这种情况下(和潜在的危险),因为这个问题是关于做一个不区分大小写的查询,如username: 'bill'匹配BILLBill不完整的文本搜索查询,这也将匹配朵朵词bill,如Billsbilled等等
达恩·达斯卡莱斯卡

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']]]);
Don Rzeszut

9

Mongo(当前版本2.0.0)不允许对索引字段进行不区分大小写的搜索-请参阅其文档。对于非索引字段,其他答案中列出的正则表达式应该可以。


19
只是为了澄清这一点:允许对索引字段进行不区分大小写的搜索,它们将不使用索引,并且速度会与未索引字段一样慢。
heavi5ide

@ heavi5ide,因为此问题用于标记重复项,所以我想我要澄清一下正则表达式(不区分大小写的搜索需要)确实使用了索引,但是,它们必须进行完整的索引扫描。换句话说,他们不能有效地使用索引。幸运的是,此文档自2011年以来已进行了更新,但在这里也要特别注意。
2014年

7

使用基于Regex的查询时要记住的一件非常重要的事情-在登录系统中执行此操作时,请转义要搜索的每个字符,并且不要忘记^和$运算符。Lodash为此提供了一个不错的功能,如果您已经在使用它的话:

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

为什么?假设有一个用户输入.*为用户名。它将匹配所有用户名,只需猜测任何用户的密码即可启用登录。


6

最好的方法是选择语言,在为对象创建模型包装时,使save()方法遍历要搜索的一组字段,这些字段也会被索引;这些字段集应具有小写字母,然后用于搜索。

每次再次保存对象时,都将检查小写属性并使用对主属性的任何更改进行更新。这样一来,您就可以有效地进行搜索,但隐藏每次更新lc字段所需的额外工作。

小写的字段可以是key:value对象存储,也可以只是带有前缀lc_的字段名称。我使用第二种方法简化查询(深对象查询有时会令人困惑)。

注意:您要索引lc_字段,而不是它们基于的主要字段。


不错的解决方案,但幸运的是从MongoDB 3.4开始,对不区分大小写的索引提供了本机支持。
Dan Dascalescu

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

使用猫鼬对我有用:

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 2015年

是的。您不需要.toLowerCase()。我已将其从答案中删除。
克里斯里奇(ChrisRich)

嗯,这应该那样工作吗?当我搜索“ mark”时,它还会获得带有“ marko”的所有记录-有没有办法只忽略区分大小写?
Suisse

确定,正确的正则表达式为:'^'+ serach_name +'$',“ i”
Suisse

3
这很危险。您没有在逃避用户名,​​因此可以插入任意正则表达式。
汤姆·梅塔姆


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

转义变量可以保护查询免受“。*”或其他正则表达式的攻击。

转义字符串正则表达式


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()方法。
Nilay

0

对于任何使用Golang并希望使用mongodb和mgo godoc globalsign库进行区分大小写的全文本搜索的人。

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


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

-1

如您在mongo docs中所见-由于版本3.2 $text索引默认情况下不区分大小写:https//docs.mongodb.com/manual/core/index-text/#text-index-case-insensitiveivity

创建一个文本索引,在查询中使用$ text运算符


使用全文搜索是错误在这种情况下(和潜在的危险),因为这个问题是关于做一个不区分大小写的查询,如username: 'bill'匹配BILLBill不完整的文本搜索查询,这也将匹配朵朵词bill,如Billsbilled等等
达恩·达斯卡莱斯卡

-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年9月发布了我的答案。我在2018年4月发布了我的答案。实际上我发布了这个,因为当时没有。在警告那些真正尝试提供帮助的人之前,还请检查它的发布时间。
Woppi

我说的是2016年4月的答案,以及2016年5月的答案。请同时使用$regex$options。您按了Ctrl + F了什么?
Dan Dascalescu

而且,使用$regex效率低下并且可能不安全,正如我在对其他2016年答案所做的编辑中所解释的那样。如果答案不再为社区服务,删除它们就不会感到羞耻!
Dan Dascalescu

注意到效率低下的$ regex,非常感谢。我按Ctrl + F $ options。我们只有两个,在$ regex代码中没有新的Regexp,分别为2018年4月和2018年9月。我在回答中没有使用新的Regexp。我忘记了新Regexp遇到的特定问题,将其删除后就解决了,只使用我发布的解决方案即可。
Woppi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.