查询数组大小大于1的文档


664

我有一个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 } }})

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


3
较新的MongoDB版本具有$ size运算符;您应该查看@tobia的答案
AlbertEngelB 2014年

4
实际解决方案:FooArray:{$ gt:{$ size:'length'}}->长度可以是任何数字
Sergi Nadal

Answers:


489

更新:

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


1,使用$ where

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

但...

Javascript的执行速度比本页上列出的本机运算符慢,但是非常灵活。有关更多信息,请参见服务器端处理页面。

2.创建额外的字段NamesArrayLength,使用名称数组长度对其进行更新,然后在查询中使用:

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

这将是更好的解决方案,并且将运行得更快(您可以在其上创建索引)。


4
太好了,太好了,谢谢。尽管实际上我有一些没有名称的文档,所以不得不将查询修改为:db.accommodations.find({$ where:“ if(this.name && this.name.length> 1){返回;}“});
艾森(Emson)2011年

不客气,是的,您可以使用中的任何javascript $where,它非常灵活。
2011年

8
@emson我认为这样做会更快,例如{“ name”:{$ exists:1},$ where:“ this.name.lenght> 1”} ...尽量减少较慢的javascript查询中的部分。我认为这种方法可行,并且$ exists的优先级更高。
nairbv 2012年

1
我不知道您可以在查询中嵌入javascript,json可能很麻烦。这些查询中有许多是一次只能手动输入的,因此不需要优化。我经常会用+1这个技巧
pferrel 2014年

3
从数组中添加/删除元素后,我们需要更新“ NamesArrayLength”的计数。可以在单个查询中完成吗?还是需要2个查询,一个用于更新数组,另一个用于更新计数?
WarLord

1325

由于您可以在查询对象键中使用数字数组索引,因此在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}}}
);

16
有人可以解释一下如何编制索引。

26
我对它的有效性以及您想找到这种解决方案的“开箱即用”的方式印象深刻。这也适用于2.6。
EarthmeL14年

2
同样适用于3.0。非常感谢您找到这个。
pikanezi

1
@Dims没什么不同,真的:{'Name Field.1': {$exists: true}}
JohnnyHK '16

9
@JoseRicardoBustosM。这将寻找到的文档name包含至少 1个元素,但OP一直在寻找更大的比1
JohnnyHK

127

我相信这是回答您问题的最快查询,因为它不使用解释$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" ] }
>

9
@viren我不知道。这肯定比Javascript解决方案要好,但是对于较新的MongoDB,您应该使用{'name.1': {$exists: true}}
Tobia

@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“ :“”名称_1_d_1“,”方向“:”转发“,” indexBounds“:{”名称“:[” [\“ abc \”,\“ abc \”]“],” d“:[” [MinKey,MaxKey ]“]}}如果您看到它扫描了整个表格。

最好将答案更新为推荐其他替代方法(例如'name.1': {$exists: true}},也因为这是硬编码为“ 1”,并且不能缩放为任意或参数化的最小数组长度。)
Dan Dascalescu

1
这可能很快,但是如果您要查找列表> N(其中N并不小),则会分崩离析。
布兰登·希尔

62

您也可以使用聚合:

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一样,是最通用的解决方案,因为它可以用于任何数组大小。
2015年

如果我想在投影中使用“ size_of_name”,那我该怎么做?实际上,我想在投影中使用$ slice,其值等于$ slice:[0,“ ​​size_of_name”-skip] ??
Sudhanshu Gaur's

44

尝试做这样的事情:

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

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


2
三年前给出相同的答案。
Dan Dascalescu

我来自未来,对此我将不胜感激:此解决方案通过检查在所述位置上是否存在元素来工作。因此,收集必须大于该数量。
MarAvFe19年

我们可以在查询中放入一些动态数字,例如“ ArrayName。<some_num>”吗?
萨希尔·马哈詹

是的,您可以使用任何数字。如果要获取大于N的记录,则传递n。
阿曼·戈尔


26

您可以使用$ 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"
Dan Dascalescu

3
@DanDascalescu似乎并非所有文档中都有邮票。当没有图章时,可以使用ifNull输出空数组。像db.col.find({$expr:{$gt:[{$size:{$ifNull:["$passport.stamps", []]}}, 1]}})
Sagar Veeram '18



13

我找到了此解决方案,以查找具有大于一定长度的数组字段的项目

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

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

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

这本质上是相同的答案是这一个,提供了2个年前。
Dan Dascalescu

1

我知道它的旧问题,但是我尝试使用$ gte和$ size查找。我认为find()更快。

db.getCollection('collectionName').find({ name : { $gte : {  $size : 1 } }})

-5

尽管上面的方法可以解决所有问题,但是您最初尝试的方法是正确的方法,但是您只需将语法向后(切换“ $ size”和“ $ gt”)。

正确:

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

不正确:

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

1
我不知道为什么要投票这么多-这对我来说非常有效!
杰克·斯托克斯

我没有投票,但是不起作用(v4.2)。
叶夫根尼·纳博科夫

完全正常,v 4.2.5
jperl
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.