【问题标题】:Mongodb many to many relations among sub-documentsMongodb 子文档之间的多对多关系
【发布时间】:2014-06-06 19:11:53
【问题描述】:

TL;DR:想象一下,第一个 $match 阶段给了你几个文档,但你想在里面细化它们,就像$redact 所做的那样。但问题是你的子文件有关系,你想在它们之间做$wherelike检查。怎么能做到这一点? 我无法 $unwind,因为它会导致性能问题,(1.5 mb 的文档,数组长度为 1000 的 5 倍,单次展开会导致 1000x~1mb 的文档)。


我的架构如下:

{
    userName: "user44",
    userID: "44",
    posts : [
        ...
        {
            title : "post1",
            id : "123"
            ...
        },
        {
            title : "post2",
            id : "124"
            ...
        },
        ...
    ],
    comments: [
        ...
        {
            id: "1910",
            postId : "123",
            commentTitle : "comment1",
            comment : "some comment",
            user: "user13"
        },
        {
            id: "1911",
            postId : "124",
            title : "comment2",
            commentTitle : "some comment",
            user: "user22"
        },
        {
            id: "1912",
            postId : "124",
            title : "comment2",
            commentTitle : "some comment",
            user: "user22"
        },
        ...
    ], 
    commentUpvotes: [
        ...
        {
            id : 12,
            commentId : "1910",
            upvotedBy: "user91",
            upvoteDate: 1000,         
        },
        {
            id: 13,
            commentId : "1910",
            upvotedBy: "user92",
            upvoteDate: 2000
        },
        {
            id: 14,
            commentId : "1911",
            upvotedBy: "user92",
            upvoteDate: 2100
        },
        ...
    ]
} 

虽然这与我的数据库无关,但原始架构与上面完全相同。所以,上面这个例子是一个用户集合,我存储了用户的postscomments 用于其他用户的帖子,commentUpvotes 用于存储有关谁支持的信息。 不要去想它的设计和内容的逻辑;我编的,请不要推荐任何其他架构。

问题:我正在寻找一种方法来查找在特定日期之后投票的帖子和 cmets,例如

 db.users.find("commentUpvotes.upvoteDate" : {$gte:0})

结果:

{
    "_id" : ObjectId("539065d3cd0f2aac5f55778e"),
    "posts" : [
        {
            title : "post1",
            id : "123"
            ...
        },
        {
            title : "post2",
            id : "124"
            ...
        },
    ],
    "comments" : [
            {
            id: 1910,
            postId : "123",
            title : "comment1",
            comment : "some comment",
            user: "user13"
        },
        {
            id: 1911,
            postId : "124",
            title : "comment2",
            comment : "some comment",
            user: "user22"
        },
    ],
    "commentUpVotes" : [
            {
            id : 12,
            commentId : "1910",
            upvotedBy: "user91",
            upvoteDate: 1000,         
        },
        {
            id: 13,
            commentId : "1910",
            upvotedBy: "user92",
            upvoteDate: 2000
        },
        {
            id: 14,
            commentId : "1911",
            upvotedBy: "user92",
            upvoteDate: 2100
        }
    ]
}

注意:这是一个后问题,前一个问题可以找到here。我想在这个中对其进行一些扩展。

【问题讨论】:

  • 我可能听起来很幼稚,但为什么您的问题以“TL;DR”开头?我相信它的意思是“太长了,没读过”。那你为什么要把它放在问题的首位。你是想阻止我们阅读这个问题吗?
  • 我喜欢给出一个小总结的想法,继续与否完全取决于你
  • 那么 TL;DR,您的意思是“您可能会发现整个文章太长而无法阅读,但这里是问题的简短摘要”?

标签: mongodb aggregation-framework


【解决方案1】:

当我在last question 上向您发表评论时,我让它坐了一会儿,执行此操作的基本过程是什么。我还评论说$redact不是这种操作的动物,除了这里的答案之外,还有两个原因需要解释。只需说您知道过滤后的值,而不仅仅是过滤它们。

与之前给出的一样,您仍然需要使用$unwind,但不是传统的用法会导致管道中要处理的文档数量激增,它只是在之后使用 数组内容已被过滤。这里唯一真正的区别是我们注意到“过滤数组”实际上将包含多个一个元素,因此您可以适当地处理它:

db.users.aggregate([
    { "$match": {
        "commentUpvotes.upvoteDate": { "$gte": 0 }
    }},
    { "$project": {
        "posts": 1,
        "comments": 1,
        "commentUpVotes": {
            "$setDifference": [
                { 
                    "$map": {
                        "input": "$commentUpvotes",
                        "as": "el",
                        "in": {
                            "$cond": [
                                { "$gte": [ "$$el.upvoteDate", 0 ] },
                                "$$el",
                                false
                            ]
                        }  
                    }
                },
                [false]
            ]
        }
    }},
    { "$project": {
         "posts": 1,
         "comments": 1,
         "kcommentUpVotes": "$commentUpVotes",
         "commentUpVotes": 1
    }},
    { "$unwind": "$commentUpVotes" },
    { "$project": {
        "posts": 1,
        "comments": {
            "$setDifference": [
                { 
                    "$map": {
                        "input": "$comments",
                        "as": "el",
                        "in": {
                            "$cond": [
                                { 
                                    "$eq": [ 
                                        { "$substr": [ "$$el.id", 0, 4 ] }, 
                                        "$commentUpVotes.commentId"
                                    ] 
                                },
                                "$$el",
                                false
                            ]
                        }  
                    }
                },
                [false]
            ]
        },
        "commentUpVotes": "$kcommentUpVotes"
    }},
    { "$unwind": "$comments" },
    { "$group": {
         "_id": "$_id",
         "posts": { "$first": "$posts" },
         "comments": { "$addToSet": "$comments" },
         "kcomments": { "$addToSet": "$comments" },
         "commentUpVotes": { "$first": "$commentUpVotes" }
    }},
    { "$unwind": "$comments" },
    { "$project": { 
        "posts": {
            "$setDifference": [
                { 
                    "$map": {
                        "input": "$posts",
                        "as": "el",
                        "in": {
                            "$cond": [
                                { 
                                    "$eq": [ 
                                        "$$el.id", 
                                        "$comments.postId"
                                    ] 
                                },
                                "$$el",
                                false
                            ]
                        }  
                    }
                },
                [false]
            ]
        },
        "comments": "$kcomments",
        "commentUpVotes": 1
    }},
    { "$unwind": "$posts" },
    { "$group": {
        "_id": "$_id",
        "posts": { "$addToSet": "$posts" },
        "comments": { "$first": "$comments" },
        "commentUpVotes": { "$first": "$commentUpVotes" }
    }}
])

所以这里有一点要准确理解每个阶段(或重复的过程)在做什么以及为什么这里的$unwind 操作很重要。

如果考虑到这里的第一个$project,返回的结果总是一个数组。这就是使用$map 进行“过滤”的方式,并且非常有意义,因为您期望有几个(在本例中为全部)匹配的可能性。

重要的部分发生在您尝试将这些值与文档中的另一个数组进行匹配之前,因为当您查看 $map 的解剖结构时,重点是将元素与奇异值进行比较。这就是为什么您需要$unwind 才能获得这些“奇异”值进行比较。

因此,除了保留“过滤”数组的副本以使事情更清晰之外,让我们跳到匹配“cmets”数组之后的部分。由于“commentUpvotes”数组被“展开”,现在每个文档都有一个副本,其中有它自己的数组过滤版本。注意每个结果数组只能包含一个元素。

因为这些确实是数组,为了在文档之间组合它们,您需要展开这些“单元素”数组,然后将它们组合在一起。请记住,虽然“commentUpvotes”有“三个”匹配项,但只有“两个”匹配项匹配,但在“三个”匹配项中,“两个”共享相同的 id。这就是使用$addToSet 进行分组变得很重要的地方,因为您不想复制匹配的帖子。

一旦所有匹配的元素都在数组中,就可以再次使用$unwind 并重复。

因此,一般前提与前面的示例和问题相同。实际上,这里的方法可以被认为是之前列表的“2.0 版”,因为它在所有情况下都可以满足单数和“多”匹配。

这里要提到的一个“警告”是这些项目确实相关并且在任何数组中都没有“孤立”细节的基本原则。显而易见的原因是,任何被测试为从一个数组匹配到另一个不匹配的数组都会导致一个空数组。可能还有其他匹配项,但如果其中一个测试为空,那么您将不得不处理生成的空数组。

最后一个注释的概念很简单,只需测试结果的$size,否则输入一个单一的值false,并在稍后阶段将其过滤掉。但出于练习的目的,我认为您的“关系”确实完好无损,并将任何额外的处理留给您自己的实现。

最终结果当然是您无需诉诸相同级别的“井喷”即可获得所需的结果,只需将未过滤的数组彼此展开并尝试匹配这些记录的相等性。

【讨论】:

  • 嗨,尼尔,感谢您的回答。我找到了一种不使用 unwind 的方法,请看一下,如果您提供一些见解会很好
【解决方案2】:

我找到了一种无需使用 $unwind 和 $redact + $$ROOT 即可使其工作的方法。如您所知,$redact 将文档从父级扫描到子级,因此为了在子文档之间进行比较,我需要使用 $$ROOT。

由于只在文档内部进行处理,我相信这是最有效的方式。如果有人提出更好的方法,我仍然会很高兴。 $redact 上的资源不多,相信下面的代码还是可以改进的:

// first query match
{
    "$match": {
        "commentUpvotes.upvoteDate": {
            "$gte": 0
        }
    }
},
// exclude commentUpvotes
{
    $redact: {
        $cond: {
            if: {
                $or: [
                    {
                        $gte: [
                            "$upvoteDate",
                            0
                        ]
                    },
                    {
                        $not: "$upvoteDate"
                    }
                ]
            },
            then: "$$DESCEND",
            else: "$$PRUNE"
        }
    }
},
// exclude comments
{
    $redact: {
        $cond: {
            if: {
                $or: [
                    {
                        $not: "$postId"
                    },
                    {
                        $anyElementTrue: { $map: {
                                input: "$$ROOT.commentUpvotes",
                                as: "el",
                                in: { $cond: { if: { $eq: [  "$$el.commentId",  "$id" ] },
                                        then: true, else: false
                                    }
                                }
                            }
                        }
                    }
                ]
            },
            then: "$$DESCEND",
            else: "$$PRUNE"
        }
    }
},
// exclude posts
{
    $redact: {
        $cond: {
            if: {
                $or: [
                    {
                        $not: "$title"
                    },
                    {
                        $anyElementTrue: {
                            $map: {
                                input: "$$ROOT.comments",
                                as: "el",
                                in: {
                                    $cond: {
                                        if: {
                                            $eq: [
                                                "$$el.postId",
                                                "$id"
                                            ]
                                        },
                                        then: true,
                                        else: false
                                    }
                                }
                            }
                        }
                    }
                ]
            },
            then: "$$DESCEND",
            else: "$$PRUNE"
        }
    }
}

【讨论】:

  • 如果我尝试对您的数据执行此操作,这将不起作用。问题是因为您正在与数组进行比较,正如我在我给出的答案中所说的那样。
  • 它对我有用,我在发布答案时编辑了架构。这可能是原因。
猜你喜欢
  • 2021-08-26
  • 1970-01-01
  • 2013-01-02
  • 2018-11-03
  • 1970-01-01
  • 2016-04-03
  • 2020-12-05
  • 2014-01-21
  • 2014-08-15
相关资源
最近更新 更多