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

时间:2015-03-03 16:58:14

标签: mongodb mongodb-query aggregation-framework

我正在尝试编写一个聚合查询来过滤两个子文档并对它们进行分组,但我似乎无法弄清楚如何使用单个查询来完成它,无论是否可行。我尝试了一些聚合和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: []
    }
]

希望这是有道理的。

1 个答案:

答案 0 :(得分:0)

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

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

通常的想法是改变结构,以便将“免费”和“保存”的结果移动到他们自己的文档中,标记为“类型”。然后,在分组回所需的_id时,您有条件地选择要使用$cond通过“类型”添加到任一阵列的项目,或者选择值false

唯一剩下的就是“过滤”数组中的false值。最好在现代版本中使用$setDifference,但也可以谨慎使用$unwind$match

您可能需要考虑通过“类型”将此数据存储为更扁平的形式,因为这是此管道中的大部分工作正在进行的操作。从那个地方开始,最后四个管道阶段相当简单。