基于两个子文档的 Mondo-DB 分组

Mondo-DB Grouping based on two subdocuments

我正在尝试编写一个聚合查询来过滤两个子文档并将它们分组,但我似乎无法弄清楚如何使用单个查询来完成它,无论是否可能。我尝试了一些聚合和 mapReduce 查询,但无法使它们工作。

这是一个示例文档:

[
    {
        id: "first user",
        read: [{
                    "id" : 'A',
                    free: true
                },
                {
                    "id" : 'B',
                    free: false
                },
                {
                    "id" : 'C',
                    free: true
                }],
        saved: [{
                    "id" : 'B',
                    free: true
                },
                {
                    "id" : 'C',
                    free: false
                }]
    },
    {
        id: "second user",
        read: [{
                    "id" : 'B',
                    free: true
                },
                {
                    "id" : 'C',
                    free: true
                }],
        saved: [{
                    "id" : 'A',
                    free: true
                }]
    }
]  

基本上,我想将用户阅读和保存的子文档分开分组,同时过滤掉非免费的子文档。这是我们想要的输入:

[
    { 
        id: 'A',
        freeRead: [ 'first user'],
        freeSaved: ['second user']
    },
    { 
        id: 'B',
        freeRead: [ 'second user'],
        freeSaved: ['first user']
    },
    { 
        id: 'C',
        freeRead: ['first user', 'second user'],
        freeSaved: []
    }
]

希望这是有道理的。

只要您在每个数组结果中使用不同的值,这就不是问题:

db.subs.aggregate([
    // Match valid documents only
    { "$match": {
        "$or": [
            { "read.free": true },
            { "saved.free": true }
        ]
    }},

    // Unwind arrays
    { "$unwind": "$read" },
    { "$unwind": "$saved" },

    // Add type array and unwind
    { "$project": {
        "_id": 0,
        "id": 1,
        "read": 1,
        "saved": 1,
        "type": { "$literal": [ "read", "saved" ] }
    }},
    { "$unwind": "$type" },


    // Group distinct values conditionally by "type"
    { "$group": {
        "_id": {
            "_id": { "$cond": [
                { "$eq": [ "$type", "read" ] },
                { "id": "$read.id", "free": "$read.free" },
                { "id": "$saved.id", "free": "$saved.free" }
            ]},
            "id": "$id",
            "type": "$type"
        }
    }},

    // Only interested in free true
    { "$match": { "_id._id.free": true } },

    // Group conditionally, one document two array fields
    { "$group": {
        "_id": "$_id._id.id",
        "freeRead": { "$addToSet": { "$cond": [
            { "$eq": [ "$_id.type", "read" ] },
            "$_id.id",
            false
        ]}},
        "freeSaved": { "$addToSet": { "$cond": [
            { "$eq": [ "$_id.type", "saved" ] },
            "$_id.id",
            false             
        ]}}
    }},

    // Filter the `false` values
    { "$project": {
        "freeRead": { "$setDifference": [ "$freeRead", [false] ] },
        "freeSaved": { "$setDifference": [ "$freeSaved", [false] ] }
    }},

    // Sort in order
    { "$sort": { "_id": 1 } }
])

一般的想法是改变结构,以便 "free" 和 "saved" 结果都被移动到它们自己的文档中,并用 "type" 标记它们是什么。然后,在分组回所需的 _id 时,您有条件地 select 使用 "type" 使用 $cond 将哪些项目添加到任一数组,或者 select 值 false.

剩下的唯一事情就是 "filter" 数组中的 false 值。最好使用 $setDifference in modern versions but can also be done with careful use of $unwind and $match.

您可能希望通过 "type" 考虑以更扁平的形式存储此数据,因为这是此管道中的大部分工作正在做的事情。从那里开始,最后四个流水线阶段相当简单。