并提前感谢您的帮助。 我有一个像这样结构的mongoDB数据库:
{
'_id' : objectID(...),
'userID' : id,
'movies' : [{
'movieID' : movieID,
'rating' : rating
}]
}
我的问题是:
我想搜索具有' userID'的特定用户? :3,例如,让一切都是电影,然后我想让所有其他用户至少有15个或更多电影使用相同的' movieID',然后使用该组我只想选择拥有这15部电影相似的用户,并且有一个额外的电影ID'我选择了。
我已经尝试过聚合,但是失败了,如果我做一个单一的查询,比如让用户收看所有用户的电影,那么骑自行车每个用户的电影并进行比较需要花费很多时间。
任何想法?
谢谢
答案 0 :(得分:11)
使用聚合框架
有两种方法可以做到这一点只是一组简单的数据,例如:
{
"_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
值下,以便其余步骤不会修改这些字段。这里的另一部分是将“电影”数组保留在文档的顶部,作为下一阶段的副本。
在为2.6版本提供的版本中也发生了什么,还有一个额外的数组代表要匹配的“电影”的_id
值。这里使用 $cond
运算符只是创建数组“文字”表示的一种方法。有趣的是,MongoDB 2.6引入了一个名为 $literal
的运算符来完成这一操作而没有我们在这里使用 $cond
的有趣方式。
$ unwind :要做进一步的操作,需要解开影片阵列,因为在任何一种情况下,它都是隔离需要的条目的现有_id
值的唯一方法与“集合”相匹配。因此对于pre 2.6版本,您需要“展开”两个存在的数组。
$ group :对于MongoDB 2.6及更高版本,您只需将一个数组分组,该数组仅包含已移除“评分”的电影的_id
值。
Pre 2.6因为所有的值都是“并排”(并且有很多重复),所以你要对两个值进行比较,看它们是否相同。如果是true
,则会告诉 $cond
运算符语句返回1
或0
的值,其中条件为{{1} }。这会直接通过 false
传回,以将数组中匹配元素的数量总计为所需的“集合”。
$ project :这是MongoDB 2.6及更高版本的不同部分,因为您已经推回了“电影”$sum
值的数组,然后使用 _id
直接比较这些数组。因此,这是一个包含相同元素的数组,然后将其包装在 $setIntersection
运算符中,以确定在该匹配集中返回了多少元素。 / p>
$ match :此处已实施的最后阶段是否执行明确步骤,仅匹配交叉元素数量大于或等于所需数量的文档。
这基本上就是你这样做的。在2.6之前有点笨拙并且需要更多的内存,因为通过复制由集合的所有可能值找到的每个数组成员来完成扩展,但它仍然是一种有效的方法。
您需要做的就是应用更大的$size
匹配值来满足您的条件,当然也要确保您的原始用户匹配具有所需的n
种可能性。否则,只需从“用户”“电影”数组的长度n
生成此内容。