使用聚合框架有几种方法可以做到这一点
只是一组简单的数据,例如:
{
"_id" : ObjectId("538181738d6bd23253654690"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 2, "rating": 6 },
{ "_id": 3, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654691"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 4, "rating": 6 },
{ "_id": 2, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654692"),
"movies": [
{ "_id": 2, "rating": 5 },
{ "_id": 5, "rating": 6 },
{ "_id": 6, "rating": 7 }
]
}
以第一个“用户”为例,现在您想要查找其他两个用户中是否有至少两个相同的电影。
对于 MongoDB 2.6 及更高版本,您可以简单地使用$setIntersection http://docs.mongodb.org/manual/reference/operator/aggregation/setIntersection/运算符以及$size http://docs.mongodb.org/manual/reference/operator/aggregation/size/操作员:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document if you want to keep more than `_id`
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
}},
// Unwind the array
{ "$unwind": "$movies" },
// Build the array back with just `_id` values
{ "$group": {
"_id": "$_id",
"movies": { "$push": "$movies._id" }
}},
// Find the "set intersection" of the two arrays
{ "$project": {
"movies": {
"$size": {
"$setIntersection": [
[ 1, 2, 3 ],
"$movies"
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
在没有这些运算符的早期版本的 MongoDB 中,这仍然是可能的,只需使用几个步骤:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document along with the "set" to match
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
"set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
}},
// Unwind both those arrays
{ "$unwind": "$movies" },
{ "$unwind": "$set" },
// Group back the count where both `_id` values are equal
{ "$group": {
"_id": "$_id",
"movies": {
"$sum": {
"$cond":[
{ "$eq": [ "$movies._id", "$set" ] },
1,
0
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
详细
这可能有点难以理解,所以我们可以看看每个阶段并将其分解,看看他们在做什么。
$match:您不想对集合中的每个文档进行操作,因此这是一个删除可能不匹配的项目的机会,即使仍然需要做更多工作来查找exact那些。因此,显而易见的事情是排除相同的“用户”,然后仅匹配至少具有与为该“用户”找到的相同电影的一部的文档。
接下来有意义的事情是考虑当你想要匹配时n
条目则仅包含“电影”数组大于的文档n-1
实际上可能包含匹配项。指某东西的用途$and
这里看起来很有趣并且没有特别要求,但是如果所需的匹配是4
那么该语句的实际部分将如下所示:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
所以你基本上“排除”了那些可能不够长的数组n
火柴。这里要注意的是,这$size http://docs.mongodb.org/manual/reference/operator/query/size/查询形式中的运算符不同于$size http://docs.mongodb.org/manual/reference/operator/aggregation/size/为聚合框架。例如,无法将其与不等式运算符一起使用,例如$gt
它的目的是专门匹配所请求的“尺寸”。因此,此查询表单指定小于的所有可能大小。
$project:此声明有几个目的,其中一些目的根据您拥有的 MongoDB 版本而有所不同。首先,也可以选择将文件副本保存在_id
值,以便其余步骤不会修改这些字段。这里的另一部分是将“电影”数组保留在文档顶部作为下一阶段的副本。
2.6 之前的版本中还出现了一个额外的数组,表示_id
要匹配的“电影”的值。的用法$cond
这里的运算符只是创建数组的“文字”表示的一种方法。有趣的是,MongoDB 2.6 引入了一个称为$literal
不用我们正在使用的有趣的方式来做到这一点$cond
就在这儿。
$unwind:要做进一步的事情,电影数组需要展开,因为在任何一种情况下,它都是隔离现有的唯一方法_id
需要与“集合”匹配的条目的值。因此,对于 2.6 之前的版本,您需要“展开”现有的两个数组。
$group:对于 MongoDB 2.6 及更高版本,您只需分组回一个仅包含以下内容的数组:_id
删除“评级”的电影的值。
在 2.6 之前,由于所有值都是“并排”呈现(并且有大量重复),因此您正在对两个值进行比较以查看它们是否相同。那是哪里true
,这告诉$cond
返回值的运算符语句1
or 0
条件是false
。这个是直接传回来的$sum
将数组中匹配元素的数量总计为所需的“集合”。
$project:这是 MongoDB 2.6 及更高版本的不同部分,因为您已经推回了一组“电影”_id
您正在使用的值$setIntersection
直接比较这些数组。由于这是一个包含相同元素的数组,因此它被包装在$size
运算符以确定该匹配集中返回了多少元素。
$match:是此处实现的最后阶段,它执行明确的步骤,仅匹配那些相交元素计数大于或等于所需数量的文档。
Final
基本上就是这样做的。 2.6 之前的版本有点笨拙,并且需要更多的内存,因为扩展是通过复制集合的所有可能值找到的每个数组成员来完成的,但这仍然是一种有效的方法。
您所需要做的就是将其应用到更大的n
匹配值以满足您的条件,当然还要确保您的原始用户匹配具有所需的n
的可能性。否则就生成这个n-1
来自“用户”的“电影”数组的长度。