我有以下情况: 我的一个mongo集合包含以下格式的文档:
user: "test",
tracks: [{artist: "A", ...}, {artist: "B", ...}, ..., { artist: "N", ...}]
我想提取所有曲目,其艺术家都在给定的数组arr
中。为此,我使用以下查询(工作正常)。
collection.find({ tracks: { $elemMatch: { artist: { $in: arr }}}})
但是,现在我想修改查询,以便它只返回集合中那些由至少让来自arr
数组的3个不同艺术家所执行的轨道的文档。我怎样才能实现这一点(除了从数据库返回结果后过滤结果,这不是一个选项)?
答案 0 :(得分:8)
你的问题对我有两种可能,但也许有一些解释可以让你开始。
首先,我需要向您解释您误解了$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 } })
因此该样本中没有匹配,并且它只匹配特定数组元素具有这两个元素的位置。
现在我们已解释$elemMatch
,这是您的简化查询:
db.collection.find({ "tracks.artist": { "$in": arr } })
更简单,它的工作原理是通过单个字段查看所有数组成员,并返回文档中的任何元素至少包含其中一个可能结果的位置。
但不是你在问什么,等你的问题。如果您仔细阅读最后一条陈述,您应该意识到$in
实际上是$or
条件。它只是一个缩短的形式,用于询问"或"在文档中的相同元素。
考虑到这一点,您所要求的核心是"" 操作,其中所有"三"值包含在内。假设你只发送了三个"测试中的项目然后您可以使用$and
的缩写形式的$all
形式:
db.collection.find({ "tracks.artist": { "$all": arr } })
那只会返回那些元素在该数组成员中匹配的文件#34; all"在测试条件中指定的元素。这可能就是你想要的,但有一种情况当然你想要指定一个说清单,"四个或更多"艺术家要测试,只想要"三"或者其中一些较小的数字,在这种情况下,$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
操作进行转换,该操作被设置为仅返回"艺术家"每个数组元素的字段。
作为"十字路口"在这两个数组中,最后测试了所得到的常见元素列表的$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 } }}
])