【问题标题】:Query for documents where array size is greater than 1查询数组大小大于 1 的文档
【发布时间】:2011-12-10 07:15:39
【问题描述】:

我有一个 MongoDB 集合,其中包含以下格式的文档:

{
  "_id" : ObjectId("4e8ae86d08101908e1000001"),
  "name" : ["Name"],
  "zipcode" : ["2223"]
}
{
  "_id" : ObjectId("4e8ae86d08101908e1000002"),
  "name" : ["Another ", "Name"],
  "zipcode" : ["2224"]
}

我目前可以获取与特定数组大小匹配的文档:

db.accommodations.find({ name : { $size : 2 }})

这会正确返回 name 数组中包含 2 个元素的文档。但是,我无法执行$gt 命令来返回所有name 字段的数组大小大于2 的文档:

db.accommodations.find({ name : { $size: { $gt : 1 } }})

如何选择大小大于 1 的 name 数组的所有文档(最好不必修改当前数据结构)?

【问题讨论】:

  • 较新版本的 MongoDB 有 $size 运算符;你应该看看@tobia的答案
  • 实际解决方案:FooArray:{$gt:{$size:'length'}} --> 长度可以是任意数字
  • @SergiNadal:我认为FooArray:{$gt:{$size:'length'}} 不起作用!至少在嵌套对象上是一个数组person:{ids:[123,456]}
  • 数组应该有一个复数名称,所以你的数组字段name应该命名为names

标签: mongodb mongodb-query


【解决方案1】:

更新:

对于 2.2+ 的 mongodb 版本,@JohnnyHK 在另一个 answer 中描述了更有效的方法。


1.使用$where

db.accommodations.find( { $where: "this.name.length > 1" } );

但是……

Javascript 的执行速度比列出的本地运算符慢 这个页面,但是非常灵活。查看服务器端处理页面 了解更多信息。

2.创建extra字段NamesArrayLength,用names数组长度更新它,然后在查询中使用:

db.accommodations.find({"NamesArrayLength": {$gt: 1} });

这将是更好的解决方案,并且工作速度更快(您可以在其上创建索引)。

【讨论】:

  • 太好了,非常感谢。虽然我实际上有一些没有名称的文档,所以不得不将查询修改为: db.accommodations.find( { $where: "if (this.name && this.name.length > 1) {return this ; } "} );
  • 不客气,是的,您可以在$where 中使用任何javascript,非常灵活。
  • @emson 我认为这样做会更快 { "name": {$exists:1}, $where: "this.name.lenght > 1"} ... 最小化较慢的 javascript 查询中的部分。我认为这是可行的,并且 $exists 将具有更高的优先级。
  • 我不知道你可以在查询中嵌入javascript,json可能很麻烦。其中许多查询都是一次性输入的,因此不需要进行优化。我会经常使用这个技巧+1
  • 从数组中添加/删除元素后,我们需要更新“NamesArrayLength”的计数。这可以在一个查询中完成吗?或者它需要 2 个查询,一个用于更新数组,另一个用于更新计数?
【解决方案2】:

我相信这是回答您问题的最快查询,因为它不使用解释型 $where 子句:

{$nor: [
    {name: {$exists: false}},
    {name: {$size: 0}},
    {name: {$size: 1}}
]}

它的意思是“所有文档,除了那些没有名称(不存在或空数组)或只有一个名称的文档。”

测试:

> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>

【讨论】:

  • @viren 我不知道。这当然比 Javascript 解决方案更好,但对于较新的 MongoDB,您可能应该使用 {'name.1': {$exists: true}}
  • @Tobia 我第一次使用的是 $exists ,但它实际上使用全表扫描非常慢。 db.test.find({"name":"abc","d.5":{$exists:true},"d.6":{$exists:true}}) "nReturned" : 46525, "executionTimeMillis “:167289,“totalKeysExamined”:10990840,“totalDocsExamined”:10990840,“inputStage”:{“stage”:“IXSCAN”,“keyPattern”:{“name”:1,“d”:1},“indexName” : "name_1_d_1", "direction" : "forward", "indexBounds" : { "name" : [ "[\"abc\", \"abc\"]" ], "d" : [ "[MinKey, MaxKey ]" ] } } 如果你看到它扫描了整个表。
  • 很高兴更新答案以推荐其他替代方案(如'name.1': {$exists: true}},并且还因为这是硬编码为“1”并且不会扩展到任意或参数化的最小数组长度。
  • 这可能会很快,但如果您要查找列表 > N,这可能会失败,其中 N 并不小。
  • 如果您正在寻找一个嵌套数组,其中内部数组的长度至少为 2,但 {'foo.bar.details.2': {$exists: true }} 会找到这些。
【解决方案3】:

现在您可以在查询对象键中使用数字数组索引(基于 0),在 MongoDB 2.2+ 中有一种更有效的方法。

// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})

您可以使用使用部分过滤器表达式的索引来支持此查询(需要 3.2+):

// index for at least two name array elements
db.accommodations.createIndex(
    {'name.1': 1},
    {partialFilterExpression: {'name.1': {$exists: true}}}
);

【讨论】:

  • 谁能解释一下如何索引这个。
  • 我对它的有效性以及您想要找到此解决方案的“开箱即用”方式印象深刻。这也适用于 2.6。
  • 也适用于 3.0。非常感谢您找到这个。
  • @JoseRicardoBustosM。这将找到 name 包含 至少 1 个元素的文档,但 OP 正在寻找 大于 的文档。
  • 在答案中提到索引是从 0 开始的会很有帮助。
【解决方案4】:

以上都不适合我。这个是这样做的,所以我正在分享它:

db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )

【讨论】:

【解决方案5】:

您也可以使用聚合:

db.accommodations.aggregate(
[
     {$project: {_id:1, name:1, zipcode:1, 
                 size_of_name: {$size: "$name"}
                }
     },
     {$match: {"size_of_name": {$gt: 1}}}
])

//你在中转文件中添加“size_of_name”,并用它来过滤名称的大小

【讨论】:

  • 这个解决方案是最通用的,与@JohnnyHK 一样,因为它可以用于任何数组大小。
  • 如果我想在投影中使用“size_of_name”,那么我该怎么做呢?实际上我想在投影中使用 $slice ,它的值等于 $slice : [0, "size_of_name" - skip] ??
【解决方案6】:
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})

【讨论】:

  • 这不能很好地扩展到其他最小尺寸(例如,10)。
  • 同第一个答案
【解决方案7】:

尝试做这样的事情:

db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})

1 是数字,如果要获取大于 50 的记录,则执行 ArrayName.50 谢谢。

【讨论】:

  • three years earlier给出了同样的答案。
  • 我们可以在查询中加入一些动态数字,例如“ArrayName.”吗?
  • 是的,您可以使用任何号码。如果要获取大于 N 的记录,则传递 n。
【解决方案8】:

我找到了这个解决方案,查找数组字段大于一定长度的项目

db.allusers.aggregate([
  {$match:{username:{$exists:true}}},
  {$project: { count: { $size:"$locations.lat" }}},
  {$match:{count:{$gt:20}}}
])

第一个 $match 聚合使用对所有文档都为 true 的参数。如果为空,我会得到 ​​p>

"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"

【讨论】:

  • 这与两年前提供的this one 的答案基本相同。
【解决方案9】:

虽然上述答案都有效,但您最初尝试做的是正确的方法,但是您只是将语法倒转(切换“$size”和“$gt”)..

正确:

db.collection.find({items: {$gt: {$size: 1}}})

不正确:

db.collection.find({items: {$size: {$gt: 1}}})

【讨论】:

  • 我不明白为什么会有这么多反对票 - 这对我来说非常有效!
  • 工作得很好,v 4.2.5
  • 在发布此类解决方案时始终发布版本。不适用于 4.2
  • 从 4.4 开始,“正确”不起作用。只是因为$gt 参数必须是数字。 $size 参数中的任何一个都必须是数字。
  • 这在 v4.4.4 中不起作用。回复摘录:"errmsg": "$size needs a number"
【解决方案10】:

您可以使用$expr(3.6 mongo 版本运算符)在常规查询中使用聚合函数。

比较 query operatorsaggregation comparison operators

db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})

【讨论】:

  • 您将如何传递作为子文档的数组而不是$name,例如在“人”记录中passport.stamps?我尝试了各种引用组合,但我得到了"The argument to $size must be an array, but was of type: string/missing"
  • @DanDascalescu 似乎所有文档中都没有邮票。当邮票不存在时,您可以使用ifNull 输出空数组。类似db.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
【解决方案11】:

MongoDB 3.6 包括 $expr https://docs.mongodb.com/manual/reference/operator/query/expr/

您可以使用 $expr 来计算 $match 中的表达式,或查找。

{ $match: {
           $expr: {$gt: [{$size: "$yourArrayField"}, 0]}
         }
}

或找到

collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});

【讨论】:

【解决方案12】:
db.inventory.find( { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } )

您可以在查询中使用$gt$lt

【讨论】:

  • 这将返回 dim_cm 数组包含范围 (22,30) 中的值的文档。与数组的 size 无关。
【解决方案13】:

你可以通过MongoDB聚合来做任务:

db.collection.aggregate([
  {
    $addFields: {
      arrayLength: {$size: '$array'}
    },
  },
  {
    $match: {
      arrayLength: {$gt: 1}
    },
  },
])

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-12
    • 2020-06-30
    • 2021-12-28
    • 1970-01-01
    • 2020-11-18
    相关资源
    最近更新 更多