Mongodb子文档之间的多对多关系

2024-04-13

TL;DR:想象一下第一个 $match 阶段给了你几个文档,但你想要refine他们在里面,就像$redact做。但问题是你的子文档有关系,而你想要$where就像他们之间的检查一样。怎样才能做到这一点呢?我无法 $unwind,因为它会导致性能问题(1.5 mb 的文档,具有 5 倍 1000 数组长度,单个展开会导致 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
        },
        ...
    ]
} 

虽然这与我的数据库无关,但原始架构与上面完全相同。所以,上面的例子是一个用户集合,我在其中存储posts用户的;comments其他用户发布的帖子,commentUpvotes存储有关谁投票的信息。不要去思考它的设计和内容的逻辑;我编造了它们,请不要建议任何其他架构。

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

 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
        }
    ]
}

NOTE: 是后题,前题可以查到here https://stackoverflow.com/questions/24059348/mongodb-1to1-relation-among-subdocuments。我想在这个中稍微扩展一下。


我让这个问题搁置了一段时间,因为我在最后一个问题 https://stackoverflow.com/q/24059348/2313887做到这一点的基本过程是什么。我还评论说$redact http://docs.mongodb.org/manual/reference/operator/aggregation/redact/不是进行此类操作的动物,除了此处的答案之外,还有两个原因需要解释。可以说您知道过滤后的值,而不仅仅是过滤它们。

正如之前所给出的,您仍然需要一些用法$unwind http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/但与会导致管道中要处理的文档数量激增的传统用法不同,它只是被使用after数组内容已被过滤。这里唯一真正的区别是,我们要注意“过滤数组”实际上将包含超过one元素,所以你可以适当地处理它:

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 http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/这里的操作很重要。

如果你选择第一个$project http://docs.mongodb.org/manual/reference/operator/aggregation/project/考虑到这里,返回的结果始终是一个数组。这就是“过滤”的方式$map http://docs.mongodb.org/manual/reference/operator/aggregation/map/有效,并且非常有意义,因为您期望有多个(在本例中为所有)匹配的可能性。

重要的部分发生在您尝试将这些值与文档中的另一个数组进行匹配之前,就像您查看一个数组的结构时一样。$map http://docs.mongodb.org/manual/reference/operator/aggregation/map/重点是将元素与奇异值进行比较。这就是为什么你需要$unwind http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/为了获得那些“奇异”值进行比较。

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

由于这些确实是数组,为了在文档之间组合它们,您需要展开这些“单元素”数组,然后将它们重新组合在一起。请记住,虽然“commentUpvotes”有“三个”匹配,但只有“两个”评论可以匹配,但是“三个”匹配中的“两个”共享相同的评论id。这是使用分组回的地方$addToSet http://docs.mongodb.org/manual/reference/operator/update/addToSet/变得很重要,因为您不想重复该匹配的帖子。

一旦所有匹配的元素都在数组中,就可以再次进行$unwind http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/并重复。

因此,总体前提与前面的示例和问题相同。事实上,这里的方法可以被认为是先前列表的“2.0 版本”,因为它将满足所有情况下的单数和“多”匹配。

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mongodb子文档之间的多对多关系 的相关文章

随机推荐