我所有的记录都有一个名为“图片”的字段。该字段是一个字符串数组。
我现在想要这个数组不为空的最新 10 条记录。
我已经用谷歌搜索了,但奇怪的是我没有找到太多关于这个的东西。我已经阅读了 $where 选项,但我想知道这对本机函数有多慢,以及是否有更好的解决方案。
即便如此,这也行不通:
ME.find({$where: 'this.pictures.length > 0'}).sort('-created').limit(10).execFind()
什么都不返回。留下没有长度位的 this.pictures
确实有效,但当然它也会返回空记录。
如果您还有没有密钥的文档,您可以使用:
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: [] } })
经过一番查看,尤其是在 mongodb 文档中,以及令人费解的部分,这就是答案:
ME.find({pictures: {$exists: true, $not: {$size: 0}}})
pictures
字段的记录。
这也可能对您有用:
ME.find({'pictures.0': {$exists: true}});
pictures.2
存在但 pictures.1
不存在的情况?
$exists
运算符是布尔值,而不是偏移量。 @tenbatsu 应该使用 true
而不是 1
。
Would there ever be a case where pictures.2 exists but pictures.1 does not?
是的,这种情况可能会发生。
pictures
是子文档而不是数组时才会发生这种情况。例如pictures: {'2': 123}
pictures
上有索引,它也会执行完整的集合扫描。
查询时您关心两件事 - 准确性和性能。考虑到这一点,我在 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
}
}
seen_events
字符串数组,该数组也被索引。用 { $gt: -Infinity }
搜索,我立即得到 0 个文档。使用 { $exists: true, $ne: [] }
,我得到的文档更有可能是 1,2m,在 FETCH 阶段浪费了大量时间:gist.github.com/N-Coder/b9e89a925e895c605d84bfeed648d82c
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. 所有其他查询都返回无效结果。
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
从 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 中
检索所有且仅“图片”为数组且不为空的文档
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' 是 null
、undefined
、0 等。
您可以使用以下任何方法来实现此目的。两者还注意不为其中没有请求的键的对象返回结果:
db.video.find({pictures: {$exists: true, $gt: {$size: 0}}})
db.video.find({comments: {$exists: true, $not: {$size: 0}}})
db.find({ pictures: { $elemMatch: { $exists: true } } })
$elemMatch
匹配包含数组字段且至少有一个元素与指定查询匹配的文档。
因此,您将所有数组与至少一个元素进行匹配。
使用 $elemMatch
运算符:根据文档
$elemMatch 运算符匹配包含数组字段的文档,其中至少一个元素匹配所有指定的查询条件。
$elemMatches
确保该值是一个数组并且它不为空。所以查询将类似于
ME.find({ pictures: { $elemMatch: {$exists: true }}})
PS 此代码的变体可在 MongoDB 大学的 M121 课程中找到。
{ $where: "this.pictures.length > 1" }
使用 $where 并传递 this.field_name.length ,它返回数组字段的大小并通过与数字比较来检查它。如果任何数组的值大于数组大小必须至少为 1。所以所有数组字段的长度都大于 1,这意味着它在该数组中有一些数据
这也有效:
db.getCollection('collectionName').find({'arrayName': {$elemMatch:{}}})
您还可以在 Mongo 运算符 $exists 上使用辅助方法 Exists
ME.find()
.exists('pictures')
.where('pictures').ne([])
.sort('-created')
.limit(10)
.exec(function(err, results){
...
});
ME.find({pictures: {$exists: true}})
就这么简单,这对我有用。
ME.find({ pictures: { $gt: [] } })
是危险的,即使在较新的 MongoDB 版本中也是如此。如果您的列表字段上有一个索引,并且在查询期间使用了该索引,您将得到意想不到的结果。例如:db.doc.find({'nums': { $gt: [] }}).hint({ _id: 1 }).count()
返回正确的数字,而db.doc.find({'nums': { $gt: [] }}).hint({ nums: 1 }).count()
返回0
。