user: "test",
tracks: [{artist: "A", ...}, {artist: "B", ...}, ..., { artist: "N", ...}]
我想提取所有的曲目,其艺术家在给定的数组arr.为此,我使用以下查询(工作正常).
collection.find({ tracks: { $elemmatch: { artist: { $in: arr }}}})
但是,现在我想修改查询,以便它只返回集合中的那些文档,这些文档至少让我们说arr数组中的3个不同的艺术家.我怎样才能实现这一点(除了从数据库返回后过滤结果,这不是一个选项)?
解决方法:
你的问题对我来说有两种可能,但也许有些解释可以让你开始.
首先,我需要向您解释您误解了$elemMatch
的意图,并且在这种情况下它被误用了.
$elemMatch
的想法是创建一个“查询文档”,它实际上应用于数组的元素.目的是在数组中的文档上具有“多个条件”,以便在成员文档中离散地匹配它,而不是在外部文档的整个数组中.即:
{
"data": [
{ "a": 1, "b": 3 },
{ "a": 2, "b": 2 }
]
}
以下查询将起作用,即使该数组中没有实际的单个元素匹配,但整个文档执行:
db.collection.find({ "data.a": 1, "data.b": 2 })
但要检查实际元素是否与这两个条件匹配,这是您使用$elemmatch的地方:
db.collection.find({ "data": { "a": 1, "b": 2 } })
因此该样本中没有匹配,并且它只匹配特定数组元素具有这两个元素的位置.
db.collection.find({ "tracks.artist": { "$in": arr } })
更简单,它的工作原理是通过单个字段查看所有数组成员,并返回文档中的任何元素至少包含其中一个可能结果的位置.
但不是你问的问题,等你的问题.如果你仔细阅读最后一个陈述,你应该意识到$in
实际上是$or
条件.它只是一个缩短的形式,用于询问文档中相同元素的“或”.
考虑到这一点,您所要求的核心是“和”操作,其中包含所有“三个”值.假设您只是在测试中发送“三个”项目,那么您可以使用$and
的形式,缩写形式为$all
:
db.collection.find({ "tracks.artist": { "$all": arr } })
这只会返回具有该数组成员中的元素的文档,该元素匹配测试条件中指定的“所有”元素.这可能就是你想要的,但有一种情况当然你想指定一个说“四个或更多”艺术家的名单来测试,只想要“三个”或更少的数字,在这种情况下一个$all
的操作符太简洁了.
但是有一种合理的方法可以解决这个问题,只需要对基本查询不可用的运算符进行一些处理,但aggregation framework可以使用它们:
var arr = ["A","B","C","D"]; // List for testing
db.collection.aggregate([
// Match conditions for documents to narrow down
{ "$match": {
"tracks.artist": { "$in": arr },
"tracks.2": { "$exists": true } // you would construct in code
}},
// Test the array conditions
{ "$project": {
"user": 1,
"tracks": 1, // any fields you want to keep
"matched": {
"$gte": [
{ "$size": {
"$setIntersection": [
{ "$map": {
"input": "$tracks",
"as": "t",
"in": { "$$t.artist" }
}},
arr
]
}},
3
]
}
}},
// Filter out anything that did not match
{ "$match": { "matched": true } }
])
第一阶段实现标准查询$match
条件,以便将文档过滤到仅“可能”匹配条件的文档.这里的逻辑情况是像以前一样使用$in
,它将找到那些文档,其中“test”数组中至少有一个元素存在于文档自己的数组中的至少一个成员字段中.
下一个子句是理想情况下应该在代码中构建的,因为它与数组的“长度”有关.这里的想法是你想要至少“三个”匹配,然后你在文档中测试的数组必须至少有“三个”元素才能满足这一要求,所以检索具有“两个”或更少数组元素的文档没有意义因为他们永远不会匹配“三”.
由于所有MongoDB查询基本上只是数据结构的表示,因此它非常容易构建.即,对于JavaScript:
var matchCount = 3; // how many matches we want
var match1 = { "$match": { "tracks.artist": { "$in": arr } } };
match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };
逻辑在于,带有$exists
的“点符号”形式测试指定索引(n-1)处元素的存在,并且数组需要至少具有该长度.
理想情况下,其余的缩小使用$setIntersection
方法,以便返回实际数组和测试数组之间的匹配元素.由于文档中的数组与“测试数组”的结构不匹配,因此需要通过$map
操作进行转换,该操作被设置为仅返回来自每个数组元素的“artist”字段.
当制作这两个阵列的“交叉点”时,最终测试所得到的常见元素列表的$size
,其中应用测试以查看这些元素中的“至少三个”被发现是共同的.
最后,您只需使用$match
条件“过滤掉”任何不正确的内容.
理想情况下,您使用MongoDB 2.6或更高版本以使这些运算符可用.对于早期版本的2.2.x和2.4.x,它仍然可以,但只需要更多的工作和处理开销:
db.collection.aggregate([
// Match conditions for documents to narrow down
{ "$match": {
"tracks.artist": { "$in": arr },
"tracks.2": { "$exists": true } // you would construct in code
}},
// Unwind the document array
{ "$unwind": "$tracks" },
// Filter the content
{ "$match": { "tracks.artist": { "$in": arr } }},
// Group for distinct values
{ "$group": {
"_id": {
"_id": "$_id",
"artist": "$tracks.artist"
}
}},
// Make arrays with length
{ "$group": {
"_id": "$_id._id",
"artist": { "$push": "$_id.artist" },
"length": { "$sum": 1 }
}},
// Filter out the sizes
{ "$match": { "length": { "$gte": 3 } }}
])