MongoDB嵌套数组交集查询

Dio*_*nes 7 mongodb aggregation-framework

并提前感谢您的帮助.我有一个mongoDB数据库结构如下:

{
  '_id' : objectID(...),

  'userID' : id,

  'movies' : [{

       'movieID' : movieID,

       'rating' : rating
   }]
 }
Run Code Online (Sandbox Code Playgroud)

我的问题是:

我想搜索具有'userID'的特定用户:3,例如,get all is movies,然后我想让所有其他用户拥有至少15个或更多具有相同'movieID'的电影,然后对于那个组我想只选择那些有相似的15部电影的用户,并且我选择一个额外的'movieID'.

我已经尝试过聚合,但是失败了,如果我做单个查询,比如从用户那里获取所有用户的电影,那么骑自行车每个用户电影并进行比较需要花费大量时间.

任何想法?

谢谢

Nei*_*unn 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 }
    ]
}
Run Code Online (Sandbox Code Playgroud)

以第一个"用户"为例,现在您要查找其他两个用户中是否有至少两个相同的电影.

对于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 } } }

])
Run Code Online (Sandbox Code Playgroud)

在没有这些运算符的早期版本的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 } } }
])
Run Code Online (Sandbox Code Playgroud)

详细地

这可能需要一点点,所以我们可以看看每个阶段并打破这些阶段,看看他们在做什么.

$ match:您不希望对集合中的每个文档进行操作,因此这是一个删除不可能匹配的项目的机会,即使还有更多工作要做,以找到确切的文档.因此,显而易见的事情是排除相同的"用户",然后仅匹配至少具有与该"用户"相同的电影之一的文档.

接下来有意义的是考虑当你想要匹配n条目时,只有那些大于"电影"数组的文档n-1可能实际上包含匹配.$and这里的使用看起来很有趣,并不是特别要求,但如果所需的匹配是4那么语句的实际部分将如下所示:

        "$and": [
            { "movies": { "$not": { "$size": 1 } } },
            { "movies": { "$not": { "$size": 2 } } },
            { "movies": { "$not": { "$size": 3 } } }
        ]
Run Code Online (Sandbox Code Playgroud)

因此,您基本上"排除"可能不足以进行n匹配的数组.请注意,$size查询表单中的此运算符$size与聚合框架不同.例如,没有办法将此与不等式运算符一起使用,例如$gt它的目的是专门匹配请求的"大小".因此,此查询表单指定小于的所有可能大小.

$ project:这个语句有一些目的,其中一些根据您拥有的MongoDB版本而有所不同.首先,并且可选地,文档副本保持在该_id值下,以便其余步骤不修改这些字段.这里的另一部分是将"电影"数组保留在文档的顶部,作为下一阶段的副本.

在为2.6版本提供的版本中也发生了什么,还有一个额外的数组代表_id要匹配的"电影" 的值.$cond这里运算符的使用只是创建数组的"文字"表示的一种方式.有趣的是,MongoDB 2.6引入了一个被称为操作的运算符,而$literal没有我们在$cond这里使用的有趣方式.

$ unwind:要做更进一步的操作,需要解开电影数组,因为在任何一种情况下,它都是隔离_id需要与"set"匹配的条目的现有值的唯一方法.因此,对于pre 2.6版本,您需要"展开"两个存在的阵列.

$ group:对于MongoDB 2.6及更高版本,你只需要分组回一个只包含已_id删除"评级"的电影值的数组.

Pre 2.6因为所有的值都是"并排"(并且有很多重复),所以你要对两个值进行比较,看它们是否相同.在那里true,这告诉$cond操作符语句返回条件的值10位置false.这将直接传回,$sum以将数组中匹配元素的数量总计为所需的"set".

$ project:这是MongoDB 2.6及更高版本的不同部分,因为你已经推回了"电影" _id值的数组,然后你用它$setIntersection来直接比较那些数组.由于这是一个包含相同元素的数组,然后将其包装在$size运算符中,以确定在该匹配集中返回了多少元素.

$ match:这里已经实现的最后阶段是明确的步骤,只匹配交叉元素数大于或等于所需数量的那些文档.


最后

这基本上就是你如何做到的.在2.6之前有点笨拙并且需要更多的内存,因为通过复制由集合的所有可能值找到的每个数组成员来完成扩展,但它仍然是一种有效的方法.

您需要做的就是使用更大的n匹配值来满足您的条件,当然也要确保您的原始用户匹配具有所需的n可能性.否则只需n-1从"用户""电影"数组的长度开始生成.