查找数组字段不为空的MongoDB记录


502

我所有的记录都有一个名为“图片”的字段。该字段是字符串数组。

我现在想要该数组不为空的最新10条记录。

我已经在Google周围搜索了,但是奇怪的是,我在这方面没有发现太多。我已经读过$ where选项,但是我想知道这对本机函数有多慢,以及是否有更好的解决方案。

即使这样,它也不起作用:

ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()

不返回任何内容。this.pictures没有长度位的保留确实有效,但是当然它也返回空记录。

Answers:


827

如果您还有没有密钥的文档,则可以使用:

ME.find({ pictures: { $exists: true, $not: {$size: 0} } })

如果涉及$ size,MongoDB不会使用索引,因此这是一个更好的解决方案:

ME.find({ pictures: { $exists: true, $ne: [] } })

从MongoDB 2.6版本开始,您可以与运算符进行比较,$gt但可能会导致意外的结果(您可以在此答案中找到详细的说明):

ME.find({ pictures: { $gt: [] } })

6
对我来说,这是正确的方法,因为它可以确保数组存在并且不为空。
LeandroCR 2015年

如何使用以下方法实现相同的功能mongoengine
Rohit Khatri

54
ME.find({ pictures: { $gt: [] } })即使在较新的MongoDB版本中,也要小心,危险。如果您在列表字段上有一个索引,并且在查询过程中使用了该索引,则会得到意外的结果。例如:db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()返回正确的数字,而db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()返回0
wojcikstefan

1
请参阅下面的详细答案,以了解为什么这可能对您不起作用:stackoverflow.com/a/42601244/1579058
wojcikstefan

6
@wojcikstefan的评论需要被批评,以防止人们使用最后的建议,实际上,在某些情况下,该建议不会返回匹配的文档。
Thomas Jung

181

经过一番仔细研究之后,尤其是在mongodb文档中,又将一些东西弄乱了,这就是答案:

ME.find({pictures: {$exists: true, $not: {$size: 0}}})

27
这行不通。我不知道这以前是否可行,但是这还会返回没有“图片”键的对象。
rdsoze 2014年

17
令人难以置信的是,答案实际上有63个否决,而@rdsoze所说的确实是对的-查询还将返回没有pictures字段的记录。
Dan Dascalescu 2014年

5
注意,如果涉及$ size link,mongoDB将不会使用索引。最好包含{$ ne:[]}并可能包含{$ ne:null}。
Levente Dobson 2015年

17
@rdsoze问题的第一行指出“我的所有记录都有一个名为“图片”的字段。该字段是一个数组”。更重要的是,这是一个非常现实且常见的场景。这个答案是正确的,它可以按照书面形式准确地解决该问题,并且因为不能解决其他问题而批评或否定它是愚蠢的。
Mark Amery

1
@Cec所有文档都说,如果在查询中使用$ size,它将不使用任何索引来为您提供更快的结果。因此,如果您在该字段上有一个索引并且要使用它,请坚持使用其他方法,例如{$ ne:[]},如果这对您有用,则将使用您的索引。
Levente Dobson

108

这也可能对您有用:

ME.find({'pictures.0': {$exists: true}});

2
真好!这也使您可以检查最小尺寸。您知道数组是否总是按顺序索引吗?是否会pictures.2存在但pictures.1不存在的情况?
anushr

2
$exists运营商是一个布尔值,不偏移。@tenbatsu应该使用true而不是1
ekillaby

2
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? 是的,这种情况可能会发生。
Bndr

@TheBndr仅当pictures是子文档而不是数组时才可能发生。例如pictures: {'2': 123}
JohnnyHK,2015年

4
这是很好而且直观的方法,但是请注意性能是否很重要-即使您有一个索引,它也会进行完整的集合扫描pictures
wojcikstefan

35

查询时,您关心两件事-准确性和性能。考虑到这一点,我在MongoDB v3.0.14中测试了几种不同的方法。

TL; DR db.doc.find({ nums: { $gt: -Infinity }})是最快和最可靠的(至少在我测试过的MongoDB版本中)。

编辑:这不再适用于MongoDB v3.6!请参阅此帖子下的评论,以寻求可能的解决方案。

设定

我插入了1k个带有w / oa列表字段的文档,1k个带有空列表的文档和5个带有非空列表的文档。

for (var i = 0; i < 1000; i++) { db.doc.insert({}); }
for (var i = 0; i < 1000; i++) { db.doc.insert({ nums: [] }); }
for (var i = 0; i < 5; i++) { db.doc.insert({ nums: [1, 2, 3] }); }
db.doc.createIndex({ nums: 1 });

我认识到,这不足以像我在下面的测试中那样认真地对待性能,但是足以表明各种查询的正确性和所选查询计划的行为。

测验

db.doc.find({'nums': {'$exists': true}}) 返回错误的结果(针对我们要完成的工作)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': {'$exists': true}}).count()
1005

-

db.doc.find({'nums.0': {'$exists': true}})返回正确的结果,但使用完整的集合扫描(COLLSCAN说明中的通知阶段)也很慢。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': {'$exists': true}}).explain()
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.doc",
    "indexFilterSet": false,
    "parsedQuery": {
      "nums.0": {
        "$exists": true
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "nums.0": {
          "$exists": true
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": [ ]
  },
  "serverInfo": {
    "host": "MacBook-Pro",
    "port": 27017,
    "version": "3.0.14",
    "gitVersion": "08352afcca24bfc145240a0fac9d28b978ab77f3"
  },
  "ok": 1
}

-

db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}})返回错误的结果。这是因为无效的索引扫描不会前进任何文档。如果没有索引,它可能很准确,但速度很慢。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $gt: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 2,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$gt": {
              "$size": 0
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "({ $size: 0.0 }, [])"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}})返回正确的结果,但是性能很差。从技术上讲,它会执行索引扫描,但随后仍会推进所有文档,然后不得不对其进行筛选)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $not: { '$size': 0 }}}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2016,
  "advanced": 5,
  "needTime": 2010,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "nums": {
            "$exists": true
          }
        },
        {
          "$not": {
            "nums": {
              "$size": 0
            }
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 2016,
    "advanced": 5,
    "needTime": 2010,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 2005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 2005,
      "executionTimeMillisEstimate": 0,
      "works": 2015,
      "advanced": 2005,
      "needTime": 10,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, MaxKey]"
        ]
      },
      "keysExamined": 2015,
      "dupsTested": 2015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $exists: true, $ne: [] }})返回正确的结果并且速度稍快,但是性能仍然不理想。它使用IXSCAN,它仅使用现有列表字段来推进文档,但随后必须一一过滤掉空列表。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $exists: true, $ne: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 1018,
  "advanced": 5,
  "needTime": 1011,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "$and": [
        {
          "$not": {
            "nums": {
              "$eq": [ ]
            }
          }
        },
        {
          "nums": {
            "$exists": true
          }
        }
      ]
    },
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 1017,
    "advanced": 5,
    "needTime": 1011,
    "needFetch": 0,
    "saveState": 15,
    "restoreState": 15,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 1005,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 1005,
      "executionTimeMillisEstimate": 0,
      "works": 1016,
      "advanced": 1005,
      "needTime": 11,
      "needFetch": 0,
      "saveState": 15,
      "restoreState": 15,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "[MinKey, undefined)",
          "(undefined, [])",
          "([], MaxKey]"
        ]
      },
      "keysExamined": 1016,
      "dupsTested": 1015,
      "dupsDropped": 10,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums': { $gt: [] }})这是危险的,因为它取决于所使用的索引,可能会导致意外的结果。这是因为无效的索引扫描不会前进任何文档。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
0
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
5

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: [] }}).explain('executionStats').executionStats.executionStages
{
  "stage": "KEEP_MUTATIONS",
  "nReturned": 0,
  "executionTimeMillisEstimate": 0,
  "works": 1,
  "advanced": 0,
  "needTime": 0,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "inputStage": {
    "stage": "FETCH",
    "filter": {
      "nums": {
        "$gt": [ ]
      }
    },
    "nReturned": 0,
    "executionTimeMillisEstimate": 0,
    "works": 1,
    "advanced": 0,
    "needTime": 0,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "docsExamined": 0,
    "alreadyHasObj": 0,
    "inputStage": {
      "stage": "IXSCAN",
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needFetch": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "keyPattern": {
        "nums": 1
      },
      "indexName": "nums_1",
      "isMultiKey": true,
      "direction": "forward",
      "indexBounds": {
        "nums": [
          "([], BinData(0, ))"
        ]
      },
      "keysExamined": 0,
      "dupsTested": 0,
      "dupsDropped": 0,
      "seenInvalidated": 0,
      "matchTested": 0
    }
  }
}

-

db.doc.find({'nums.0’: { $gt: -Infinity }}) 返回正确的结果,但性能不佳(使用完整收集扫描)。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).count()
5
MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums.0': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "COLLSCAN",
  "filter": {
    "nums.0": {
      "$gt": -Infinity
    }
  },
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 2007,
  "advanced": 5,
  "needTime": 2001,
  "needFetch": 0,
  "saveState": 15,
  "restoreState": 15,
  "isEOF": 1,
  "invalidates": 0,
  "direction": "forward",
  "docsExamined": 2005
}

-

db.doc.find({'nums': { $gt: -Infinity }})令人惊讶的是,这很好用!它提供了正确的结果,并且速度很快,从索引扫描阶段前进了5个文档。

MacBook-Pro(mongod-3.0.14) test> db.doc.find({'nums': { $gt: -Infinity }}).explain('executionStats').executionStats.executionStages
{
  "stage": "FETCH",
  "nReturned": 5,
  "executionTimeMillisEstimate": 0,
  "works": 16,
  "advanced": 5,
  "needTime": 10,
  "needFetch": 0,
  "saveState": 0,
  "restoreState": 0,
  "isEOF": 1,
  "invalidates": 0,
  "docsExamined": 5,
  "alreadyHasObj": 0,
  "inputStage": {
    "stage": "IXSCAN",
    "nReturned": 5,
    "executionTimeMillisEstimate": 0,
    "works": 15,
    "advanced": 5,
    "needTime": 10,
    "needFetch": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "keyPattern": {
      "nums": 1
    },
    "indexName": "nums_1",
    "isMultiKey": true,
    "direction": "forward",
    "indexBounds": {
      "nums": [
        "(-inf.0, inf.0]"
      ]
    },
    "keysExamined": 15,
    "dupsTested": 15,
    "dupsDropped": 10,
    "seenInvalidated": 0,
    "matchTested": 0
  }
}

感谢您非常详细的回答@wojcikstefan。不幸的是,您的建议解决方案似乎不适用于我。我有一个包含2m个文档的MongoDB 3.6.4集合,其中大多数具有seen_eventsString数组,该数组也已建立索引。使用搜寻{ $gt: -Infinity },我立即得到0个文件。使用{ $exists: true, $ne: [] }我,我得到了更可能的1,2m文档,而在FETCH阶段浪费了很多时间:gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
NCode

看来您是对的@Ncode-在MongoDB v3.6中不再起作用:(我玩了几分钟,发现的结果是:1. db.test_collection.find({"seen_events.0": {$exists: true}})不好,因为它使用了集合扫描。2. db.test_collection.find({seen_events: {$exists: true, $ne: []}})是不好,因为其IXSCAN匹配所有文档,然后在慢速FETCH阶段执行了过滤; 3。同样适用db.test_collection.find({seen_events: {$exists: true, $not: {$size: 0}}}); 4。其他所有查询均返回无效结果;
wojcikstefan,

1
@NCode找到了解决方案!如果您确定所有非空seen_events都包含字符串,则可以使用以下命令:db.test_collection.find({seen_events: {$gt: ''}}).count()。要确认其性能良好,请签出db.test_collection.find({seen_events: {$gt: ''}}).explain(true).executionStats。您可以通过模式验证来强制看到的事件是字符串:docs.mongodb.com/manual/core/schema-validation
wojcikstefan

谢谢!所有现有的值都是字符串,因此我将尝试一下。MongoDB Bugtracker中
NCode

30

从2.6版本开始,另一种方法是将字段与一个空数组进行比较:

ME.find({pictures: {$gt: []}})

在外壳中进行测试:

> db.ME.insert([
{pictures: [1,2,3]},
{pictures: []},
{pictures: ['']},
{pictures: [0]},
{pictures: 1},
{foobar: 1}
])

> db.ME.find({pictures: {$gt: []}})
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a7"), "pictures": [ 1, 2, 3 ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4a9"), "pictures": [ "" ] }
{ "_id": ObjectId("54d4d9ff96340090b6c1c4aa"), "pictures": [ 0 ] }

因此,它适当地包含pictures至少包含一个数组元素的docs,并排除其中pictures为空数组,非数组或缺失的docs 。


7
注意如果尝试使用索引,此答案可能会给您带来麻烦。这样做db.ME.createIndex({ pictures: 1 }),然后db.ME.find({pictures: {$gt: []}})将返回零个结果,至少在MongoDB中v3.0.14
wojcikstefan

@wojcikstefan好抓住。需要对此进行重新审视。
JohnnyHK '17

5

您可以使用以下任何一种方法来实现。
两者还注意不要为其中没有请求的键的对象返回结果:

db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})

4

仅检索“图片”为数组且不为空的所有文档

ME.find({pictures: {$type: 'array', $ne: []}})

如果使用3.2之前的MongoDb版本,请使用$type: 4代替$type: 'array'。注意,该解决方案甚至不使用$ size,因此索引没有问题(“查询无法将索引用于查询的$ size部分”)

其他解决方案,包括以下这些(可接受的答案):

ME.find({图片:{$ exists:true,$ not:{$ size:0}}}); ME.find({图片:{$ exists:true,$ ne:[]}})

错误的,因为即使例如'pictures'为,,0等null,它们也会返回文档undefined


2

使用$elemMatch操作员:根据文档

$ elemMatch运算符匹配包含一个包含至少一个与所有指定查询条件匹配的元素的数组字段的文档。

$elemMatches确保该值是一个数组并且不为空。因此查询将类似于

ME.find({ pictures: { $elemMatch: {$exists: true }}})

PS在MongoDB大学的M121课程中可以找到此代码的变体。


0

您还可以在Mongo运算符$ exists上使用Exists方法

ME.find()
    .exists('pictures')
    .where('pictures').ne([])
    .sort('-created')
    .limit(10)
    .exec(function(err, results){
        ...
    });

0
{ $where: "this.pictures.length > 1" }

使用$ where并传递this.field_name.length,它返回数组字段的大小,并通过与数字比较来检查它。如果任何数组的值大于数组大小的值必须至少为1,则所有数组字段的长度均大于一,这表示该数​​组中有一些数据


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.