使用聚合框架有几种方法可以做到这一点
只是一组简单的数据例如:
{
"_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 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 :您不想对集合中的每个文档进行操作,因此这是一个删除可能不匹配的项目的机会,即使还有更多工作要做确切的。因此,显而易见的事情是排除相同的“用户”,然后只匹配至少有一部与该“用户”相同的电影的文档。
接下来要考虑的是,当您想要匹配 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 :要进一步做任何事情,都需要展开 movies 数组,因为在任何一种情况下,它都是隔离需要匹配的条目的现有 _id 值的唯一方法“集”。因此,对于 2.6 之前的版本,您需要“展开”现有的两个数组。
$group :对于 MongoDB 2.6 及更高版本,您只是分组回一个数组,该数组仅包含已删除“评分”的电影的 _id 值。
在 2.6 之前,因为所有值都“并排”呈现(并且有很多重复),所以您正在比较这两个值以查看它们是否相同。那里是true,这告诉$cond 运算符语句返回一个值1 或0,条件是false。这直接通过 $sum 传回,以将数组中匹配元素的数量总计为所需的“集合”。
$project:这是 MongoDB 2.6 及更高版本的不同之处在于,由于您已将“电影”_id 值的数组推回,因此您正在使用 @ 987654353@ 直接比较这些数组。结果是一个包含相同元素的数组,然后将其包装在 $size 运算符中,以确定在该匹配集中返回了多少元素。
$match:是此处实施的最后阶段,它执行明确的步骤,仅匹配那些相交元素的数量大于或等于所需数量的文档。
决赛
这基本上就是你的做法。 2.6 之前的版本有点笨重,并且由于通过复制集合的所有可能值找到的每个数组成员来完成扩展,因此需要更多的内存,但它仍然是一种有效的方法。
您需要做的就是使用更大的n 匹配值来满足您的条件,当然还要确保您的原始用户匹配具有所需的n 可能性。否则,只需根据“电影”的“用户”数组的长度在n-1 上生成它。