MongoDB中字符串字段值的长度限制

118

该字段的数据类型为字符串。我想获取字段名称字符长度大于40的数据。

我尝试了以下查询,但返回错误。 1.

db.usercollection.find(
{$where: "(this.name.length > 40)"}
).limit(2);

output :error: {
    "$err" : "TypeError: Cannot read property 'length' of undefined near '40)' ",
    "code" : 16722
}

这在2.4.9中可行,但我的版本是2.6.5。


1
你试过使用“gt”而不是“>”吗? - gpullen
6个回答

232

对于MongoDB 3.6及更高版本:

$expr 操作符允许在查询语言中使用聚合表达式,因此您可以利用$strLenCP 操作符来检查字符串的长度,如下所示:

db.usercollection.find({ 
    name: { $exists: true },
    $expr: { $gt: [{ $strLenCP: '$name' }, 40] } 
})

对于MongoDB 3.4及更高版本:

您还可以使用聚合框架和$redact管道操作符来处理逻辑条件,该操作符允许您使用$cond运算符处理逻辑条件,并使用特殊操作$$KEEP将逻辑条件为真的文档“保留”或$$PRUNE将逻辑条件为假的文档“删除”。

此操作类似于具有$project管道的操作,该管道选择集合中的字段并创建一个新字段,其中包含逻辑条件查询的结果,然后是一个$match,但$redact使用单个管道阶段更有效。

至于逻辑条件,有字符串聚合运算符可用,您可以使用$strLenCP操作符来检查字符串的长度。如果长度$gt特定值,则这是一个真实匹配,并且文档被“保留”。否则,它将被“修剪”和丢弃。


请考虑运行以下演示上述概念的聚合操作:

db.usercollection.aggregate([
    { $match: { name: { $exists: true } } },
    { $redact: {
         $cond: [
            { $gt: [ { $strLenCP: "$name" }, 40] },
            "$$KEEP",
            "$$PRUNE"
        ]
    } },
    { $limit: 2 }
])
如果使用$where,请尝试在查询中去掉括号:
db.usercollection.find({ $where: "this.name.length > 40" }).limit(2);

更好的查询方式是先检查该字段是否存在,然后再检查其长度:

db.usercollection.find({ name: { $type: 2 }, $where: "this.name.length > 40" }).limit(2); 
或者:
db.usercollection.find({ name: { $exists: true }, $where: "this.name.length > 
40" }).limit(2); 
MongoDB在执行$where表达式之前会先评估非$where查询操作,而且非$where查询语句可以使用索引。更好的性能是将字符串长度存储为另一个字段,然后您可以对其进行索引或搜索;与此相比,应用$where会慢得多。推荐在无法以任何其他方式结构化数据或处理少量数据时,作为最后一招使用JavaScript表达式和$where运算符。
另一种不使用$where运算符的不同且更快的方法是$regex运算符。考虑以下模式,其中搜索
db.usercollection.find({"name": {"$type": 2, "$regex": /^.{41,}$/}}).limit(2); 

注意 - 来自文档:

如果字段存在索引,则 MongoDB 会将正则表达式与索引中的值进行匹配,这可能比集合扫描更快。如果正则表达式是“前缀表达式”则可以进一步优化,这意味着所有潜在的匹配都以相同的字符串开头。这使 MongoDB 可以从该前缀构建一个“范围”,并仅针对在该范围内的索引值进行匹配。

如果正则表达式以插入符号 (^) 或左锚点 (\A) 开头,后跟一串简单符号,则该正则表达式为“前缀表达式”。例如,正则表达式 /^abc.*/ 通过仅与以 abc 开头的索引值匹配来进行优化。

此外,虽然 /^a//^a.*//^a.*$/ 匹配等效字符串,但它们具有不同的性能特征。所有这些表达式都使用适当的索引(如果存在); 但是,/^a.*//^a.*$//^a/ 慢。 /^a/ 可以在匹配前缀后停止扫描。


所有三个查询都正常工作。但第一个查询只接受15个字符,即“this.name.length > 15”。如果我们输入16个或更多字符,它会给出相同的错误。 - SURYA GOKARAJU
如果我们想要执行类似于profile.name这样的内部文档,你能否提供一个语法建议。 - SURYA GOKARAJU
1
对于嵌入式文档字段,请尝试使用 db.usercollection.find({"profile.name": {$type: 2}, $where: "this.profile.name.length > 40"}).limit(2); - chridam
2
工作得很完美,但我在想,是否还有其他的方法来做到这一点。就像Mongo Way db.collection.find({'country.length':{$gt:20}}) 这样的东西。 - rummykhan
1
@chridam的回答非常完美!但是我想强调一下,错误更多与检查记录中字段的存在性有关,而不是括号的存在/缺失。即使有/没有括号,对我来说都不能起作用。因此,db.usercollection.find({name: {$exists: true}, $where: "this.name.length > 40"}).limit(2);是正确的查询语句,它总是能够起作用。 - Yahya
显示剩余3条评论

28

如果文档过多,带有$where$expr的查询会变慢。

使用$regex$where$expr要快得多。

db.usercollection.find({ 
  "name": /^[\s\S]{40,}$/, // name.length >= 40
})

or 

db.usercollection.find({ 
  "name": { "$regex": "^[\s\S]{40,}$" }, // name.length >= 40
})

这个查询的意思相同于

db.usercollection.find({ 
  "$where": "this.name && this.name.length >= 40",
})

or

db.usercollection.find({ 
    "name": { "$exists": true },
    "$expr": { "$gte": [ { "$strLenCP": "$name" }, 40 ] } 
})

我为我的集合测试了每个查询。

# find
$where: 10529.359ms
$expr: 5305.801ms
$regex: 2516.124ms

# count
$where: 10872.006ms
$expr: 2630.155ms
$regex: 158.066ms

9

这是在MongoDB中实现此目的的一种方法。

db.usercollection.find({ $where: 'this.name.length < 4' })

4
这个查询使用了JavaScript表达式,因此无法利用MongoDB的索引,导致查询速度较慢。 - shahin mahmud
1
$where 在免费的 MongoDB SaaS 层中也可能不被允许使用(例如,在 Atlas 512MB 计划中就不被允许)。 - Dan Dascalescu
没错。据我所知,即使是 AWS Document DB 目前也不支持它。 - Rajdeep Gautam

7

此查询将给出字段值和长度:

db.usercollection.aggregate([
{
    $project: {
        "name": 1,
        "length": { $strLenCP: "$name" }
    }} ])

1
如果不想使用聚合函数,则可以使用以下代码: db.collection.find({$expr: {$lt: [{$strLenCP: "$name"}, 20]}}) - lesolorzanov

6

查找名称为40个或更多字符的name

db.usercollection.find({name: /.{40}/})

(从Fumiya Karasawa的答案中简化了正则表达式)


3

我遇到了类似的情况,但是在我的案例中,字符串不是一级属性,它在一个对象内部。在这里,我找不到合适的答案。所以我想分享我的解决方案给大家(希望这可以帮助任何遇到类似问题的人)。

Parent Collection 

{
"Child":
{
"name":"Random Name",
"Age:"09"
}
}

例如:如果我们需要获取仅具有子项名称长度大于10个字符的集合。

 db.getCollection('Parent').find({$where: function() { 
for (var field in this.Child.name) { 
    if (this.Child.name.length > 10) 
        return true;

}
}})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接