ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我已经用谷歌搜索了,但奇怪的是我没有找到太多关于这个的东西。我已经阅读了 $where 选项,但我想知道这对本机函数有多慢,以及是否有更好的解决方案。

即便如此,这也行不通:

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

什么都不返回。留下没有长度位的 this.pictures 确实有效,但当然它也会返回空记录。


r
rpatel

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

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

如果涉及 $size,MongoDB 不会使用索引,所以这里有一个更好的解决方案:

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

如果您的属性可能包含无效值(如 null boolean 或其他),那么您可以按照建议的 in this answer 添加额外的检查 using $types

使用 mongo >= 3.2:

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

使用 mongo < 3.2:

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

从 MongoDB 2.6 版本开始,您可以与运算符 $gt 进行比较,但这可能会导致意想不到的结果(您可以找到详细解释 in this answer):

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

对我来说这是正确的方法,因为它确保数组存在并且不为空。
小心,ME.find({ pictures: { $gt: [] } }) 是危险的,即使在较新的 MongoDB 版本中也是如此。如果您的列表字段上有一个索引,并且在查询期间使用了该索引,您将得到意想不到的结果。例如:db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count() 返回正确的数字,而 db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count() 返回 0
请参阅下面的详细回答,了解为什么这可能不适合您:stackoverflow.com/a/42601244/1579058
@wojcikstefan 的评论需要得到支持,以防止人们使用最后一个建议,事实上,在某些情况下,它不会返回匹配的文档。
使用 $exists 会返回图片为空、未定义等的文档。这里是 better solution
b
bgran

经过一番查看,尤其是在 mongodb 文档中,以及令人费解的部分,这就是答案:

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

这行不通。我不知道这是否以前有效,但这也会返回没有“图片”键的对象。
令人难以置信的是,这个答案有 63 个赞成票,而事实上 @rdsoze 所说的是真的 - 查询还将返回 没有 具有 pictures 字段的记录。
请注意,如果涉及 $size link,mongoDB 将不会使用索引。最好包括 {$ne:[]} 和可能的 {$ne:null}。
@rdsoze 问题的第一行指出“我的所有记录都有一个名为“图片”的字段。该字段是一个数组”。更重要的是,这是一个非常现实和常见的场景。这个答案没有错,它完全按照书面形式适用于该问题,并且因为它不能解决不同的问题而批评或否决它是愚蠢的。
@Cec所有文档都说,如果您在查询中使用 $size ,它将不会使用任何索引来为您提供更快的结果。因此,如果您在该字段上有索引并且想要使用它,请坚持使用其他方法,例如 {$ne:[]},如果这对您有用,那将使用您的索引。
D
Dan Dascalescu

这也可能对您有用:

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

好的!这也可以让您检查最小尺寸。你知道数组是否总是按顺序索引吗?是否会出现 pictures.2 存在但 pictures.1 不存在的情况?
$exists 运算符是布尔值,而不是偏移量。 @tenbatsu 应该使用 true 而不是 1
@anushr Would there ever be a case where pictures.2 exists but pictures.1 does not? 是的,这种情况可能会发生。
@TheBndr 只有当 pictures 是子文档而不是数组时才会发生这种情况。例如pictures: {'2': 123}
这很好也很直观,但如果性能很重要,请注意 - 即使您在 pictures 上有索引,它也会执行完整的集合扫描。
w
wojcikstefan

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

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

编辑:这不再适用于 MongoDB v3.6!请参阅此帖子下的评论以获取潜在的解决方案。

设置

我插入了 1k 个带有 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_events 字符串数组,该数组也被索引。用 { $gt: -Infinity } 搜索,我立即得到 0 个文档。使用 { $exists: true, $ne: [] },我得到的文档更有可能是 1,2m,在 FETCH 阶段浪费了大量时间:gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
看来你是对的@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. 所有其他查询都返回无效结果。
@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
谢谢!所有现有值都是字符串,所以我会尝试一下。 MongoDB bugtracker 中还有一个讨论此问题的错误:jira.mongodb.org/browse/SERVER-26655
使用 mongodb shell 并解释这种方法给了我最有效的查询,干得好,谢谢!到目前为止,这是我想出如何使用 c# 驱动程序表达它的最佳方法: var builder = Builders.Filter; var filter = builder.Gt("myarray", Decimal128.NegativeInfinity); myObjects = col.findOne(filter).ToList();
J
JohnnyHK

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

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

在 shell 中测试它:

> 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 具有至少一个数组元素的文档,并排除了 pictures 为空数组、不是数组或缺失的文档。


如果您尝试使用索引,小心这个答案可能会给您带来麻烦。执行 db.ME.createIndex({ pictures: 1 }) 然后执行 db.ME.find({pictures: {$gt: []}}) 将返回零结果,至少在 MongoDB v3.0.14 中
@wojcikstefan 不错。需要重新审视这一点。
S
SC1000

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

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' 是 nullundefined、0 等。


Q
Quentin Hayot

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

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

B
Banane
db.find({ pictures: { $elemMatch: { $exists: true } } })

$elemMatch 匹配包含数组字段且至少有一个元素与指定查询匹配的文档。

因此,您将所有数组与至少一个元素进行匹配。


A
Andres Moreno

使用 $elemMatch 运算符:根据文档

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

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

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

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


P
Prabhat Yadav
{ $where: "this.pictures.length > 1" }

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


D
Dave

这也有效:

db.getCollection('collectionName').find({'arrayName': {$elemMatch:{}}})

E
Eat at Joes

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

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

L
Luis Fletes
ME.find({pictures: {$exists: true}}) 

就这么简单,这对我有用。


如果不为空,问题就不是。如果它是空的。