考虑一个收集分数的文档集合作为嵌套文档数组,如下所示(我使用MongoDb 4.2):
{
"scores" : [
{ "value" : 2 },
{ "value" : 3 },
{ "value" : 4 }
]
}, {
"scores" : [
{ "value" : 8 },
{ "value" : 9 },
{ "value" : 10 }
]
}, {
"scores" : [
{ "value" : 7 },
{ "value" : 7 },
{ "value" : 10 }
]
}
现在考虑以下用例:"给我所有只包含[X, Y]范围内分数的文档"。换句话说,请过滤掉至少有一个值不在[X-Y]范围内的文档。旁注:在实际数据中,分数是实数,所以在这里做一些$in查询是没有用的。
让我们考虑三个范围:
- 范围[8,10](一份文件应匹配)
- Range[0,1](0文档匹配)。
- Range[5,6](0文档匹配)。
作为mongo的非专家,我的第一次尝试是这样做的:
db.test.find({
$and: [
{ "scores.value": {$gte: 8}},
{ "scores.value": {$lte: 10}}
]
})
- Case[8,10]: 2条记录。
- Case [0,1]: 0 records
- Case[5,6]: 2个记录
所以这里有一些奇怪的东西:第一个结果表明,我的查询实际上返回的分数满足条件中的任何一个(有点像$或而不是$和)。但如果这是真的,我将检索第二个情况[0,1]上的所有记录,而不是0。很明显我在这里用错了,但如果有人能告诉我为什么,我会很高兴的。
下查询:
db.test.find({
"scores": {
$elemMatch: {
value: {$gte: 6.5, $lte: 6.8}
}
}
})
- Case[8,10]: 2条记录。
- Case [0,1]: 0 records
- Case [5,6]: 0 records
对于那个,一切都很好,并且按照文档/预期工作,但它不符合我最初的用例。
所以除了理解使用第一个查询发生了什么,有人知道一种方法来实现我想要做的吗?
以[8-10]为例,正如你提到的,你可以编写一个查询来查找所有的"坏"至少有一个分数不在[8-10]范围内的文件:这就是elementMatch
的目的。
"糟糕的;文件是(score <8 OR分数>10):
db.collection.find({
"scores": {
$elemMatch: {
$or: [ { value: { $lt: 8 }}, { value: { $gt: 10 }} ]
}
}
})
然后您可以使用$not
反转条件以获得您期望的结果(拒绝"坏")文档):
db.collection.find({
"scores": {
$not: {
$elemMatch: {
$or: [ { value: { $lt: 8 }}, { value: { $gt: 10 }} ]
}
}
}
})
MongoDB操场
让我们从了解你第一次尝试的错误开始。
你正在使用Mongo的点符号,这是怎么做的?
按从零开始的索引位置指定或访问数组元素
基本上它一次求值所有数组元素
$and: [
{ "scores.value": {$gte: 8}},
{ "scores.value": {$lte: 10}}
]
这些表达式中的每一个都查询整个数组,这意味着要满足它的查询,您所需要的只是一个在范围内的单个值。
您可以使用$not操作符并结合$elemMatch
查找要排除的元素,如:
db.collection.find({
"scores": {
$not: {
$elemMatch: {
$or: [
{
value: {
$lt: 8
}
},
{
value: {
$gt: 10
}
}
]
}
}
}
})
Mongo操场