【问题标题】:MongoDB - Group documents then sum nested subdocumentsMongoDB - 对文档进行分组,然后对嵌套的子文档求和
【发布时间】:2019-01-30 21:54:35
【问题描述】:

我有一个 mongo 集合,其中包含我想要汇总字段的子文档的文档 - 下面是我希望实现的示例

每个文档的大体结构是

{
    "pool" : "Foo",
    "monthly-figures" : {
        "1": {
            "a" : 311,
            "b" : 1481,
            ...
            "x" : {"a" : 311, "b" : 19.965999999999998},
            "y" : {"a" : 200, "b" : 14.174000000000003
            }
        },
        "2": {
            "a" : 500,
            "b" : 100,
            ...
            "x" : {"a" : 123, "b" : 198},
            "y" : {"a" : 200, "b" : 13.7}
        },
        ... // May not all be present
        "12": {...}
        }
    }
}

monthly 存储为对象而不是数组的原因是某些月份可能不存在。

以三个文件为例

{
    "pool" : "Foo",
    "monthly-figures" : {
        "1": {
            "a" : 10,
            "b" : 20,
            ...
            "x" : {"a" : 15, "b" :30}
            }
        },
        "2": {
            "a" : 500,
            "b" : 100,
            ...
            "x" : {"a" : 40, "b" : 50},
        },
        "7": {
            "a": 300,
            "b": 90,
            ...
            "x": {"a": 4, "b": 5}
        }
    }
}

{
    "pool" : "Foo",
    "monthly-figures" : {
        "1": {
            "a" : 15,
            "b" : 25,
            ...
            "x" : {"a" : 20, "b" : 35},
        },
        "2": {
            "a" : 250,
            "b" : 200,
            ...
            "x" : {"a" : 60, "b" : 80},
        }
    }
}


{
    "pool" : "Bar",
    "monthly-figures" : {
        "1": {
            "a" : 300,
            "b" : 400,
            ...
            "x" : {"a" : 51, "b" : 3},
            }
        },
        "6": {
            "a" : 75,
            "b" : 135,
            ...
            "x" : {"a" : 12.5, "b" : 16},
        }
    }
}

我想通过聚合实现的是基于 pool 字段进行分组,然后将 monthly-figures 中包含的值相加 - 所以生成的两个文档看起来像

{
    "pool" : "Foo",
    "monthly-figures" : {
        "1": {
            "a" : 25,
            "b" : 45,
            ...
            "x" : {"a" : 35, "b" : 65},
        },
        "2": {
            "a" : 750,
            "b" : 300,
            ...
            "x" : {"a" : 100, "b" : 130},
        },
        "7": {
            "a": 300,
            "b": 90,
            ...
            "x": {"a": 4, "b": 5}
        }
    }
}

(带有pool 的 Bar 的文档将是相同的,因为只有 1 个)

聚合后一个月是否全为 0 值无关紧要(如果说该月不存在于分组的任何文档中),但理想情况下不存在?

我提出了这个确实有效的查询,但我觉得这不是最好的方法 - 大量重复 - 我该如何改进?

{$group: {
        // Group to pool
        _id: "$pool",

        // Sum grouped documents
        "1a": {$sum: "$monthly-figures.1.a"},
        "1b": {$sum: "$monthly-figures.1.b"},
        ...
        "1xa": {$sum: "$monthly-figures.1.x.a"},
        "1xb": {$sum: "$monthly-figures.1.x.b"},


        "2a": {$sum: "$monthly-figures.2.a"},


        ... Continue all the way down to 12
    }
},
{$project: {
        "_id": 0,
        "pool": "$_id",

        "monthly-figures": {

            "1": {
                "a": "$1a",
                "b": "$1b",
                ...
                "x": {
                    "a": "$1xa",
                    "b": "$1xb"
                }
            },
            "2": {
                "a": "$2a",
                ...
            }

            ... Continue all the way down to 12
        }
    }
}

关于更清洁的管道有什么想法吗? 干杯!

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    消除每月列出的需要的一种方法是将每月数字对象转换为数组。你说:

    monthly 存储为对象而不是数组的原因是某些月份可能不存在。

    但数组仍然可以工作,因为您可以:

    [{month: 1, figures: {...}}, {month: 6, figures: {...}}]
    

    使用monthly-figures 一个数组,我们可以将数组的每个元素$unwind 放入自己的文档中。现在可以在池和月份上执行$group 以获得总和。为了收集每个池的月份,我们可以在池上再做一个 $group$push 特殊形成的对象,包含月份和数字,到一个称为每月数字的数组中。这些对象很特殊,因为 k 和 v 键被 $arrayToObject 运算符识别,该运算符在下一阶段用于取回原始表单。这是查询:

    db.colx.aggregate([{
        "$project": {
            "pool": 1,
            "monthly-figures": {"$objectToArray": "$monthly-figures"}
        }
    }, {
        "$unwind": "$monthly-figures"
    }, {
        "$group": {
            "_id": {
                "pool": "$pool",
                "month": "$monthly-figures.k",      
            },
            "a": {"$sum": "$monthly-figures.v.a"},
            "b": {"$sum": "$monthly-figures.v.b"},
            "x_a": {"$sum": "$monthly-figures.v.x.a"},
            "x_b": {"$sum": "$monthly-figures.v.x.b"}
        }
    }, {
        "$group": {
            "_id": "$_id.pool",
            "monthly-figures": {
                "$push": {
                    "k": "$_id.month",
                    "v": {
                        "a": "$a",
                        "b": "$b",
                        "x": {
                            "a": "$x_a",
                            "b": "$x_b"
                        }
                    }
                }
            }
        }
    }, {
        "$project": {
            "_id": 0,
            "pool": "$_id",
            "monthly-figures": {"$arrayToObject": "$monthly-figures"}
        }
    }])
    

    这是查询的输出:

    {
        "pool" : "Bar",
        "monthly-figures" : {
            "6" : {
                "a" : 75,
                "b" : 135,
                "x" : {
                    "a" : 12.5,
                    "b" : 16
                }
            },
            "1" : {
                "a" : 300,
                "b" : 400,
                "x" : {
                    "a" : 51,
                    "b" : 3
                }
            }
        }
    }
    {
        "pool" : "Foo",
        "monthly-figures" : {
            "1" : {
                "a" : 25,
                "b" : 45,
                "x" : {
                    "a" : 35,
                    "b" : 65
                }
            },
            "2" : {
                "a" : 750,
                "b" : 300,
                "x" : {
                    "a" : 100,
                    "b" : 130
                }
            },
            "7" : {
                "a" : 300,
                "b" : 90,
                "x" : {
                    "a" : 4,
                    "b" : 5
                }
            }
        }
    }
    

    MongoDB 文档链接:

    【讨论】:

    • 太棒了。我确实想知道使用 $objectToArray 但想不出它如何适合管道。对我的理解有很大帮助 - 谢谢!
    猜你喜欢
    • 1970-01-01
    • 2016-06-28
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    • 2017-07-25
    • 1970-01-01
    相关资源
    最近更新 更多