使用聚合框架有几种方法可以做到这一点。
以下是一个简单的数据集示例:
{
"_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
运算符以及$size
运算符:
db.users.aggregate([
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
}},
{ "$unwind": "$movies" },
{ "$group": {
"_id": "$_id",
"movies": { "$push": "$movies._id" }
}},
{ "$project": {
"movies": {
"$size": {
"$setIntersection": [
[ 1, 2, 3 ],
"$movies"
]
}
}
}},
{ "$match": { "movies": { "$gte": 2 } } }
])
在MongoDB早期版本中仍然可以实现,只需要多执行几个步骤,而不使用这些操作符:
db.users.aggregate([
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
"set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
}},
{ "$unwind": "$movies" },
{ "$unwind": "$set" },
{ "$group": {
"_id": "$_id",
"movies": {
"$sum": {
"$cond":[
{ "$eq": [ "$movies._id", "$set" ] },
1,
0
]
}
}
}},
{ "$match": { "movies": { "$gte": 2 } } }
])
详细说明
可能需要一些时间来理解,因此我们可以逐个阶段查看并分解以查看它们在做什么。
$match:您不希望对集合中的每个文档进行操作,因此这是一个机会,可以排除那些未必是匹配项的项目,即使还有更多工作要做才能找到确切的匹配项。因此,明显的事情是排除相同的“用户”,然后只匹配与该“用户”找到的至少一个相同电影的文档。
下一件有意义的事情是考虑当你想要匹配n
个条目时,只有具有大于n-1
的“电影”数组的文档可能实际包含匹配项。这里使用$and
看起来很奇怪,并不是特别必需的,但如果所需的匹配数为4
,则语句的实际部分将如下所示:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
因此,您基本上“排除”长度不足以具有n
个匹配项的数组。需要注意的是,查询形式中的$size
运算符与聚合框架中的$size
不同。例如,无法使用不等运算符,如$gt
,因为它的目的是专门匹配请求的“大小”。因此,这种查询形式用于指定所有可能比其小的大小。
$project:该语句有几个目的,其中一些取决于您拥有的MongoDB版本而有所不同。首先(可选),正在保留文档副本在_id
值下,以便这些字段不会被其他步骤修改。这里的另一部分是将“movies”数组保留在文档顶部,作为下一阶段的副本。
在针对2.6版本之前的版本呈现的版本中,还存在一个表示“电影”的_id
值的附加数组。这里使用$cond
运算符的用途只是创建“字面”数组的一种方式。有趣的是,MongoDB 2.6引入了一个名为$literal
的运算符,以完全执行此操作,而不需要我们在此处使用$cond
的有趣方式。
$unwind:为了进一步处理电影数组,必须对其进行展开,因为无论如何,这都是隔离需要与“集”匹配的条目的现有_id
值的唯一方法。因此,在2.6版本之前的版本中,您需要“展开”存在的两个数组。
$group:对于MongoDB 2.6及更高版本,您只需分组回包含删除“ratings”的电影_id
值的数组。
对于2.6版本之前的版本,由于所有值都呈现在“并排”(且存在大量重复),因此需要比较两个值,以查看它们是否相同。如果为true
,则告诉$cond
运算符语句返回 1
的值,或者在条件为false
时返回0
的值。这直接通过$sum
传递回来,以将数组中匹配元素的数量总计到所需的“set”。
$project: 在MongoDB 2.6及更高版本中的不同之处在于,由于您已经推回了“电影”
_id
值的数组,因此您随后使用
$setIntersection
直接比较这些数组。由于其结果是包含相同元素的数组,因此将其包装在
$size
运算符中,以确定在该匹配集中返回了多少个元素。
$match: 这里实现的最终阶段是仅匹配那些交错元素计数大于或等于所需数量的文档的清晰步骤。
那基本上就是这样做的方式。 在2.6之前有点笨重,并且由于通过复制由集合的所有可能值找到的每个数组成员而进行的扩展会需要更多的内存,但这仍然是一种有效的方法。
你需要做的就是将它与更大的
n
匹配值一起应用以满足您的条件,并且当然要确保您的原始用户匹配具有所需的
n
个可能性。 否则,只需根据“用户”的“电影”数组的长度生成
n-1
。